Unsubscribe link injected automatically
Why it matters
Manual unsubscribe link inclusion means that any template published without that link violates CAN-SPAM and GDPR Article 21 simultaneously. In 2024, Gmail and Yahoo added a hard requirement for List-Unsubscribe-Post headers on all bulk sends — without it, messages are increasingly routed to spam or rejected outright. Relying on individual template authors to remember the link is the systematic failure mode: teams under deadline drop it, new contributors don't know the requirement exists, and automated campaign cloning propagates the omission silently.
Severity rationale
High because missing unsubscribe mechanisms violate CAN-SPAM and GDPR requirements, and the absence of `List-Unsubscribe-Post` headers causes Gmail bulk sends to be filtered or rejected.
Remediation
Inject the unsubscribe footer HTML and both required headers automatically in the ESP adapter layer, keyed on message.emailType, in lib/email/providers/sendgrid.ts:
const isMarketing = message.emailType === 'marketing'
const unsubUrl = isMarketing
? `${process.env.APP_URL}/unsubscribe?token=${message.unsubscribeToken}`
: undefined
await sgMail.send({
...basePayload,
html: isMarketing
? message.html + `<p style="font-size:12px"><a href="${unsubUrl}">Unsubscribe</a></p>`
: message.html,
headers: isMarketing ? {
'List-Unsubscribe': `<${unsubUrl}>`,
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
} : {}
})
Transactional emails must be explicitly typed so they are not injected; the adapter, not the template author, owns this responsibility.
Detection
-
ID:
unsubscribe-auto-inject -
Severity:
high -
What to look for: Check whether unsubscribe links are automatically appended to outgoing marketing emails, or whether template authors must manually include them. Relying on manual inclusion creates a risk that bulk or drip campaign templates are published without the required unsubscribe mechanism. Also check for the presence of the
List-UnsubscribeandList-Unsubscribe-Postheaders, which are required by Gmail and Yahoo for bulk senders as of 2024. -
Pass criteria: The email sending pipeline automatically appends an unsubscribe link and both
List-UnsubscribeandList-Unsubscribe-Postheaders to all marketing messages before sending to the ESP. Transactional emails are explicitly opted out. Count all marketing template send paths — 100% must include automatic unsubscribe injection. Do NOT pass when the link is present but theList-Unsubscribe-Postheader is missing — Gmail requires both as of 2024. -
Fail criteria: Unsubscribe links must be manually included in each template. No
List-Unsubscribeheader is added. Some marketing templates have been published without an unsubscribe mechanism. -
Skip (N/A) when: The application only sends transactional emails — confirmed by reviewing all send paths and template types.
-
Detail on fail:
"No automatic unsubscribe injection — 3 of 8 campaign templates do not include an unsubscribe link"or"List-Unsubscribe header not set — bulk sends will fail Gmail's 2024 sender requirements" -
Remediation: Inject unsubscribe headers and links in the ESP adapter layer:
// lib/email/providers/sendgrid.ts async send(message: EmailMessage): Promise<{ messageId: string }> { const isMarketing = message.emailType === 'marketing' const unsubscribeUrl = isMarketing ? `${process.env.APP_URL}/unsubscribe?token=${message.unsubscribeToken}` : undefined await sgMail.send({ to: message.to, from: process.env.EMAIL_FROM!, subject: message.subject, html: isMarketing ? message.html + `<p style="font-size:12px;color:#999"> <a href="${unsubscribeUrl}">Unsubscribe</a> </p>` : message.html, text: message.text, headers: isMarketing ? { 'List-Unsubscribe': `<${unsubscribeUrl}>`, 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click' } : {} }) }
External references
- gdpr · Art. 21 — Right to Object (unsubscribe from direct marketing)
- external · can-spam-act — CAN-SPAM Act — Section 5(a)(3) opt-out mechanism
- external · gmail-yahoo-bulk-sender-2024-unsubscribe — Google/Yahoo One-Click Unsubscribe Requirement (2024) — RFC 8058
Taxons
History
- 2026-04-18·v1.0.0·Initial import from sending-pipeline-infrastructure·automated