CAN-SPAM §5(a)(4) requires unsubscribe mechanisms remain functional for at least 30 days after an email is sent. A token stored in Redis with a 7-day TTL breaks this requirement silently — every email delivered after day 7 contains a link that returns 404 or 410. CWE-613 (Insufficient Session Expiration) captures the pattern: tokens tied to campaigns, Redis TTLs, or short-lived sessions all create a window in which delivered emails become irrevocable but non-actionable, which is the definition of a broken opt-out mechanism under federal law.
Medium because the violation only manifests after the token expiry window passes, but at that point every already-delivered email in inboxes contains a broken unsubscribe link in violation of CAN-SPAM.
Store unsubscribe tokens at the subscriber level in your primary database — never in Redis with a TTL, and never keyed to a campaign record that can be deleted.
// Prisma schema — per-subscriber token, no expiry
// model Subscriber {
// id String @id @default(cuid())
// email String @unique
// unsubToken String @unique @default(cuid()) // stable, lives forever
// marketingOptOut Boolean @default(false)
// optOutAt DateTime?
// }
// Use the token as a stable footer URL in every email:
// https://example.com/api/unsubscribe?token=${subscriber.unsubToken}
If you rotate tokens after opt-out for security reasons, keep the old token in a revoked_unsub_tokens table that still resolves to the subscriber's opt-out record — never hard-delete a token that was included in a delivered email.
ID: email-sms-compliance.unsubscribe.mechanism-functional-30-days
Severity: medium
What to look for: Enumerate every relevant item. CAN-SPAM requires that unsubscribe links remain functional for at least 30 days after the email is sent. Check how unsubscribe tokens are generated and stored. Are tokens single-use and immediately invalidated after one click (which would break subsequent clicks from the same email)? Are tokens stored in the database with a reference to the subscriber (not the campaign), so they remain valid even after campaigns are archived or deleted? Check whether token expiry is set — if tokens expire in 7 or 14 days, the mechanism may fail within the 30-day window. Check whether deleting or archiving a campaign also deletes the unsubscribe tokens associated with it.
Pass criteria: Unsubscribe tokens (or equivalent subscriber-level identifiers) remain valid for at least 30 days after the email was sent. The mechanism works regardless of whether the campaign or email record is still active. Tokens are keyed to the subscriber, not the campaign, so they survive campaign cleanup.
Fail criteria: Tokens expire in fewer than 30 days. Campaign deletion removes tokens, breaking unsubscribe links from older emails. Token is single-use and gets invalidated on first click, so a user who forwards the email cannot use the link from the forwarded copy.
Skip (N/A) when: The application sends no commercial or marketing email.
Cross-reference: For broader data handling practices, the Data Protection audit covers data lifecycle management.
Detail on fail: Example: "Unsubscribe tokens have a 7-day TTL in Redis. Emails sent on day 1 will have broken unsubscribe links from day 8 onward, violating the 30-day CAN-SPAM requirement." or "Token tied to campaign_id; deleting campaign records removes the token lookup, breaking unsubscribe links in already-delivered emails.".
Remediation: Store unsubscribe tokens at the subscriber level with a long-lived or no expiry:
// Database schema — token belongs to the subscriber, not the campaign
// Prisma schema example:
// model Subscriber {
// id String @id @default(cuid())
// email String @unique
// unsubToken String @unique @default(cuid()) // stable, per-subscriber
// marketingOptOut Boolean @default(false)
// optOutAt DateTime?
// createdAt DateTime @default(now())
// }
// The token is generated once per subscriber and never expires.
// Include it in every marketing email footer:
// `https://example.com/api/unsubscribe?token=${subscriber.unsubToken}`
// If you want token rotation after opt-out, generate a new token but keep the
// old one mapped in a separate revocations table — never delete tokens that
// may still be in already-delivered emails.
Do not store tokens in Redis with a TTL. Store them in your primary database. The token should represent the subscriber's identity for opt-out purposes, not a short-lived session.