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.
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.
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.
ID: sending-pipeline-infrastructure.template-engine.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-Unsubscribe and List-Unsubscribe-Post headers, 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-Unsubscribe and List-Unsubscribe-Post headers 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 the List-Unsubscribe-Post header is missing — Gmail requires both as of 2024.
Fail criteria: Unsubscribe links must be manually included in each template. No List-Unsubscribe header 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'
} : {}
})
}