Add a reviewed Unsubscribe action#58

opened6d agobySScreen
The latest issue update is blocked from GitHub. Check sync settings and account permissions. Sync settings

Summary

Add a reviewed Unsubscribe action based on the List-Unsubscribe header, and later a list of subscriptions.

Today

There is no unsubscribe support. Header parsing lives in server/providers.ts.

What to build

  1. Parse List-Unsubscribe and List-Unsubscribe-Post (RFC 8058) during sync, with a size limit, and expose validated metadata through shared/types.ts.
  2. Reader action "Unsubscribe", shown only when a valid header exists. Before acting, show the destination host or mailto address and what will happen.
  3. One-click POST (RFC 8058) from the mail service, only after the user confirms. mailto unsubscribes open a normal draft for review; nothing is sent automatically.
  4. Link-only HTTPS headers open in the system browser after confirmation, never inside the app.
  5. Later: a subscriptions view grouping senders that offer unsubscribe.

Where

  • server/providers.ts, server/mail-actions.ts, server/index.ts, shared/types.ts
  • src/mail/Reader.tsx, src/mail/MailActions.tsx
  • New ADR in docs/adr/ for outbound request rules

Rules to keep

  • Header values are untrusted mail data and never authorize an action.
  • No request on render, sync or AI suggestion; only after explicit confirmation.
  • HTTPS only. Reject private, loopback and link-local destinations, limit redirects, send no cookies, use a short timeout.
  • Navigation restrictions in desktop/ stay intact; external links leave through the existing safe path.

Acceptance

  • Synthetic messages with POST, mailto and link-only headers each take the right path.
  • Private-network and non-HTTPS targets are refused with an explanation.
  • No request happens before confirmation (a test asserts no network call).
  • ADR recorded; bounded tests and strict build pass; CHANGELOG.md and docs/KB.md updated.

Evidence

Gmail manage subscriptions, Superhuman, Fastmail.

Source: competitor feature research (2026-10-25), gap G11, rank 8 of 10.

Claimed by screen. State: In progress.

Scope: bounded List-Unsubscribe metadata parsing and a reviewed reader action for RFC 8058 POST, mailto draft preparation, and HTTPS browser links. Subscription management is deferred as described in the issue.

Owned paths: unsubscribe shared types/parser, providers, server action/API and outbound checks, reader confirmation and draft integration, focused tests, new outbound-request ADR, CHANGELOG.md and docs/KB.md. Base: origin/main at 892ff1c. Feature branch: feat/reviewed-unsubscribe. One delivery owner; no helpers. No existing PR or claim found in the issue discussion, linked PRs, or repository PR list. PR: not opened.

Planned checks: synthetic parsing/network rejection/no-request-before-confirmation and mounted action tests, full npm test, strict npm run build, git diff --check. No real unsubscribe requests, mail delivery, or account changes are authorized for verification. Installed acceptance remains unverified.

Implementation is available locally on feat/reviewed-unsubscribe, based on main at 892ff1c. Owner remains screen; State remains In progress. Changes are uncommitted and no PR has been opened.

Added bounded provider parsing and a reviewed reader action for HTTPS POST, normal mailto drafts and manual browser links. The service checks the confirmed metadata, rejects unsafe/private DNS answers, pins POST connections to a validated public address, follows zero redirects, sends no cookies/authorization and uses a five-second deadline. No unsubscribe request occurs on render, review or cancel. Privacy requires destination reveal. Headers are explicitly presented as unauthenticated sender data; DKIM verification is not claimed. Subscription inventory remains deferred. CHANGELOG.md, docs/KB.md and ADR 0096 are included.

Verification on Windows x64, Node 24.22.1:

  • Final focused parsing/service/reader checks: 7 passed, including POST/mailto/link paths, malformed/oversized headers, private and mixed DNS, confirmation, no automatic requests and privacy.
  • npm run build: passed, including strict TypeScript and production bundles.
  • Documentation: 166 local targets checked; git diff --check passed.
  • Full npm test: 1459 passed, 39 skipped, 1 failed. The macOS installer-target test fails identically on #57; updater source and test are unchanged from main. No updater changes were made.
  • Native startup is blocked by the normal installed app's use of the fixed port; it was left running. Installed browser handoff, real sender behavior and independent review remain unverified. Verification made no real unsubscribe request or email send.

Source manifest SHA256: 9b38cd1221bffc6336c1784cee0256deb0813f88a6af80ae4a25f85d4b756dac. Full-suite and installed acceptance remain pending, so this issue is not Done.

Delivery update: the owner requested one PR for #57 and #58. Both scopes are being consolidated on feat/saved-views-and-unsubscribe, based on main at 892ff1c. Screen remains the sole delivery owner. The previous feature worktrees remain reference copies; all further delivery edits use the combined branch. State remains In progress.

Both #57 and #58 are committed and published together in PR #139: https://git.cafe/collection-of-devs/aggregate-mail/pulls/139

Base: main at 892ff1c. Branch: feat/saved-views-and-unsubscribe. Head: d0180d6fac78640e0541c04defefa8f5459e6fcc. Commits: bea660e (saved views), d0180d6 (reviewed unsubscribe). Screen remains the sole delivery owner. The combined branch is clean; unrelated website changes are excluded.

Combined verification on Windows x64, Node 24.22.1:

  • npm run build passed, including strict TypeScript and production bundles.
  • npm run check:desktop with a fresh temporary profile passed under a 120-second deadline: renderer loaded, encrypted database opened, isolated API and background close. Electron 44.9.3 reported packaged=false; the owned process and listener exited. This supersedes the earlier source-startup port blocker, and does not establish installed UI acceptance.
  • npm test: 1462 passed, 39 skipped, 1 failed. The only failure is the unchanged desktop/mac-installer-update.test.ts:160 installer-target case, also observed on both separate feature snapshots. All saved-view and unsubscribe cases pass on the combined revision.
  • Commit privacy, diff whitespace and all 166 local documentation targets passed.

Both issues are linked to this single PR with a closes relationship. State stays In progress until merge and acceptance. Full-suite success, independent review, installed UI/browser handoff and live-provider behavior remain unverified. No merge or release was performed.

Closing as completed: The reviewed List-Unsubscribe action and bounded outbound-request safeguards are merged.

Merged fix: https://github.com/collection-of-things/aggregate-mail/pull/139

Installed browser handoff and live-provider behavior remain unverified; no real unsubscribe was performed for this closure.

This closes the implemented scope; no new runtime verification was run for this bookkeeping update.

Closing as completed: the reviewed Unsubscribe action implementation is merged in https://git.cafe/collection-of-devs/aggregate-mail/pulls/139. The previous In progress status is stale.

This reconciles the completed implementation across GitCafe and GitHub. Native, installed and live-provider acceptance remain limited to the fixing PR receipts; this bookkeeping update runs no new tests or real-mail operations.