One-click unsubscribe flow
Vetrix implements RFC 8058 one-click unsubscribe. Every notification-category email carries two headers:
List-Unsubscribe: <mailto:unsubscribe@...>,
<https://<host>/email/unsubscribe?token=<raw>>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Transactional slugs (password_reset, smtp_test) never carry
these headers — those emails don't represent recurring contact.
Token lifecycle
- The Deliverer mints a 32-byte random token on every
notification-category send when
email.list_unsubscribe_enabledis true. - Only the SHA-256 hex of the token is persisted (in
email_unsubscribe_tokens); the raw token appears exclusively in the outbound header. - Tokens expire after 30 days.
Consume paths
POST /api/v1/email/unsubscribe?token=<raw>— the one-click endpoint Gmail / Yahoo hit automatically. Returns{ "status": "unsubscribed", "slug": "issue.assigned" }on success,410 Goneon expired / already-used / missing tokens.GET /api/v1/email/unsubscribe?token=<raw>— redirects to/email/unsubscribedfor clients that still issue GETs on the header URL./email/unsubscribe— public server-rendered page that server-side POSTs the consume endpoint and renders one of four states (success,already-consumed,expired,invalid).
Security
- Token is single-use — the consume path runs
UPDATE email_unsubscribe_tokens SET revoked_at = NOW() WHERE token_hash = $1 AND revoked_at IS NULL AND expires_at > NOW() RETURNING *so a concurrent double-click can't both succeed. - Rate-limited at 10 requests per IP per hour via the existing
internal/ratelimitharness. - The
invalidstate on the public page never distinguishes missing from malformed tokens → no enumeration oracle.