Skip to content

Unsubscribe only when the reader confirms, not when a link scanner opens the email link #445

Closed

Description

@HMarzban

Summary

The unsubscribe link in our emails applies the change on a plain GET. Mail link scanners, for example Microsoft Safe Links, open every link in a message. So a reader can be unsubscribed without ever clicking. RFC 8058 expects the change on POST; mail clients already use our separate one-click POST endpoint.

  • Severity: Low (not a security issue; lost notifications)
  • Area: email unsubscribe
  • Source: security review of 2026-10-06, review of email flows

Where

  • Rewrite: apps/webapp/next.config.js:205 sends /unsubscribe to ${restApi}/email/unsubscribe.
  • GET applies the change: apps/hocuspocus.server/src/api/email.ts (handler near :373, which calls apply_unsubscribe at :361).
  • The RFC 8058 POST handler already exists: apps/hocuspocus.server/src/api/email.ts:448-482.

Fix plan

  1. GET /email/unsubscribe?token= verifies the token and renders a confirmation page with one "Unsubscribe" button. It changes nothing.
  2. The button POSTs the token to the same path. The POST applies the change and renders the success page.
  3. Keep the existing List-Unsubscribe-Post handler unchanged.
  4. Page copy follows Simplified English and the email page style already used by the handler.

Acceptance criteria

  • Opening the link with GET changes no preference.
  • Pressing the button unsubscribes, as today.
  • One-click unsubscribe from the mail client still works.
  • An invalid or expired token still shows the current error page.

Verify

  • cd apps/hocuspocus.server && bun test with a new case for GET (no change) and POST (change), in the existing email test file if one exists.

Activity

  1. added theissue type on Oct 6, 2026
  2. HMarzban commented on Oct 9, 2026

    @HMarzban
    CollaboratorAuthor

    Fixed and live.

    • Commits: 659029c36, f0aae97f5
    • Deployed in bbdaae66c (production run 37976785981, green).
    • Verified on production: a GET of an unsubscribe link renders a page and writes nothing. Only the Unsubscribe button posts the change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions