# Unsubscribe link injected automatically

- **Pattern:** `ab-002467` (`sending-pipeline-infrastructure.template-engine.unsubscribe-auto-inject`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002467
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002467)

## 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`:

```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-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:

  ```ts
  // 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
- external can-spam-act — https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business
- external gmail-yahoo-bulk-sender-2024-unsubscribe — https://support.google.com/mail/answer/81126

Taxons: regulatory-conformance, privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-002467
