Skip to content

fix(inbox): keep the List-Unsubscribe target of a later message in a scan batch - #36694

Open
hhbb958 wants to merge 1 commit into
elizaOS:developfrom
hhbb958:fix/inbox-unsubscribe-batch-mechanism
Open

hhbb958 wants to merge 1 commit into
elizaOS:developfrom
hhbb958:fix/inbox-unsubscribe-batch-mechanism

Conversation

@hhbb958

@hhbb958 hhbb958 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Problem

scanEmailSubscriptions reports a sender as manual_only with no target when the
newest message from that sender has no List-Unsubscribe header — even when an
older message in the same scan batch carries one.

Steps to reproduce (deterministic, no LLM, no network):

  1. Search returns two messages from shop@example.com, newest first:
    • newest: headers: {} (a one-off "Your order shipped")
    • older: headers: { "List-Unsubscribe": "<mailto:leave@lists.example.com?subject=unsubscribe>" }
  2. new InboxUnsubscribeService(runtime, { gmail }).scanEmailSubscriptions({ maxMessages: 2 })

Actual result before the fix:

{ "method": "manual_only", "mailto": null, "httpUrl": null, "manualOnlyCount": 1 }

Expected: method: "mailto" with the target, and manualOnlyCount: 0.

Why this is a bug: the scan summary is what the agent and the UI read to decide
whether the sender can be unsubscribed. Reporting manual_only and
blocked_no_mechanism for a sender that publishes a target is a fabricated
absence of data, not a real absence.

Root cause

plugins/plugin-inbox/src/inbox/unsubscribe-service.ts:334-380.
Each message computes options / method / listUnsubscribePost, but only the
first message for a sender (the if (!existing) branch) stores them. The
existing branch incremented messageCount, collected ids and subjects, and
dropped the later message's headers. Gmail returns newest-first, and senders do
not put List-Unsubscribe on every message, so the anchor message decides the
whole sender record.

Fix

In the existing branch, when the record still has neither an https nor a mailto
target and the current message has one, copy that message's target and its
List-Unsubscribe-Post marker, and take unsubscribeMethod from the same
message so the method and the stored target can never disagree. One file, 16
lines, no behaviour change when the first message already had a mechanism.

Verification

  • New regression test in the existing real-runtime/PGlite file
    plugins/plugin-inbox/test/inbox.real-db.test.ts ("keeps the
    List-Unsubscribe target of a message that is not the batch's first").
  • With the test and without the fix:
    bunx vitest run test/inbox.real-db.test.ts -t "not the batch's first"
    Test Files  1 failed (1)
         Tests  1 failed | 12 skipped (13)
    
  • With the fix, whole file:
    bunx vitest run test/inbox.real-db.test.ts
    ✓ test/inbox.real-db.test.ts (13 tests) 11908ms
    Test Files  1 passed (1)
         Tests  13 passed (13)
    
  • Standalone repro script (not committed), before → after:
    REPRO_BROKEN: the batch publishes a mailto target but the sender reports manual_only
    REPRO_FIXED
    
  • bunx tsc --noEmit -p tsconfig.json → exit 0, no output
  • bunx @biomejs/biome check src/inbox/unsubscribe-service.ts test/inbox.real-db.test.ts → "Checked 2 files in 421ms. No fixes applied."
  • bun build src/inbox/unsubscribe-service.ts --target=node → "Bundled 985 modules in 1897ms"

Scope

2 files, +66/−1 (16 lines of source, 50 lines of test). No formatting change, no
unrelated files, no refactor.

Made via @SlopDotCash
AI provider/model: deepseek / deepseek-v4-flash
Client / agent tooling: hermes-agent
Contribution skill revision: N/A - ordinary GitHub contribution without optional run evidence
Attribution status: self-reported
— [contributor]

@hermesagent270-commits hermesagent270-commits left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed commit 94a2dbcd9830e00555187ffa059b832f3a681d39.

The code fix is correct in the tested path. I ran the new case with the production InboxUnsubscribeService and real PGlite in a network-disabled container. The case fails against the parent commit because the sender stays manual_only with a null mailto target. It passes at this PR head and returns the later message's mailto target. I also confirmed that the method, target, and List-Unsubscribe-Post value come from one message.

The PR cannot pass the required evidence gate. The body does not use the current PR template. It has no exact evidence-head marker and none of the required evidence rows. node packages/scripts/pr-evidence.ts verify 36694 reports all eight fields as missing.

Please copy the current PR template into the body. Set the marker to 94a2dbcd9830e00555187ffa059b832f3a681d39. Keep every evidence row. Put the before-and-after PGlite transcript directly in the backend logs row, and mark each inapplicable row as N/A with its reason. Then run the evidence verifier again. No source change is requested.

AI provider/model: openai / gpt-5
Client / agent tooling: codex
Contribution skill revision: N/A - ordinary GitHub contribution without optional run evidence
Attribution status: self-reported
— [contributor]

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants