Skip to content

ADR-0001. Token-based unsubscribe (no auth, no session)

FieldValue
StatusAccepted
Date2026-05-01
Decidersoutreach-team
Supersedes

Context

  • Newsletter recipients must be able to unsubscribe from a one-click link in an email, without logging in.
  • A subscriber is a public, unauthenticated actor — there is no JWT, no session, no merchant scope.
  • We need a way to authorize "deactivate this exact subscription" that is safe to embed in a plaintext email URL and cannot be used to enumerate or deactivate other people's subscriptions.

Decision

Each Subscriber row carries an unsubscribeToken, generated via IdGenerator.nextId() (a 64-bit Snowflake) on insert. The unsubscribe link is GET /subscribers/unsubscribe?token=<unsubscribeToken> and requires no authentication.

SubscriberService.unsubscribe() looks the token up; a hit sets status=DEACTIVATED and stamps unsubscribedAt; a miss throws UNSUBSCRIBE_INVALID_TOKEN (HTTP 404). The token is declared in the model's hiddenProperties, so it is never returned by any read endpoint — the only way to obtain it is to receive the email.

Consequences

ProsCons
One-click unsubscribe with zero auth frictionToken is a bearer secret in a plaintext email URL
Token never leaks via the API (hidden property)No expiry / rotation — a leaked link works indefinitely
Snowflake space (64-bit) makes guessing infeasibleRe-subscribe issues no new token (token is stable across deactivate/reactivate)
Idempotent: re-hitting the link on an already-deactivated row is harmless404 on bad token slightly leaks "token does not exist"

Alternatives Considered

OptionProsConsWhy rejected
Signed JWT in the linkBuilt-in expiry, no DB columnRequires key management; long ugly URLs; subscriber has no account to scope toOverkill for a public list
Email + confirmation stepVerifies ownershipExtra friction; defeats one-click expectationHurts unsubscribe completion (and legal one-click intent)
Random UUID tokenSame UXNeeds separate generator; Snowflake already available via IdGeneratorSnowflake reuses existing infra

References

  • packages/core/src/models/schemas/outreach/subscriber/schema.ts (unsubscribeToken $defaultFn, hiddenProperties)
  • packages/outreach/src/services/subscriber.service.ts (unsubscribe)
  • packages/outreach/src/errors/subscriber.errors.ts (UNSUBSCRIBE_INVALID_TOKEN)
  • packages/outreach/src/controllers/subscriber/definitions.ts (UNSUBSCRIBE route)

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.