A per-campaign suppression list means an unsubscribe from Campaign A has no effect on Campaign B, C, or triggered automations. This is a direct CAN-SPAM § 5 violation: the law requires that opt-outs apply to all commercial email from the sender within 10 business days, not just the campaign the unsubscribe link appeared in. GDPR Art. 21 right to object similarly applies globally — an objection to marketing email must stop all marketing email, not one drip sequence. Per-campaign suppression is architecturally convenient (each campaign manages its own list) but legally and operationally incorrect.
High because per-campaign-only suppression is a structural CAN-SPAM § 5 violation that allows continued sends to unsubscribed contacts across campaigns, exposing the sender to FTC enforcement action.
Enforce a single global suppression table consulted before every send path — marketing, transactional, triggered, and drip:
async function isSuppressed(email: string): Promise<boolean> {
const row = await db.suppression.findFirst({
where: { email: email.toLowerCase().trim() }
})
return row !== null
}
// Apply in every dispatch path:
async function dispatchEmail(to: string, template: string, data: object) {
if (await isSuppressed(to)) {
await db.sendLog.create({ data: { to, template, outcome: 'suppressed' } })
return
}
await esp.send({ to, template, data })
}
Audit every send path (campaign scheduler, transactional trigger, automation worker, drip sequence processor) and add the isSuppressed call if it is missing. Per-campaign exclusion lists can remain for content targeting, but the global suppression check must precede all of them.
ID: data-quality-list-hygiene.suppression-bounce.global-suppression-list
Severity: high
What to look for: Enumerate all email send paths in the system (marketing campaigns, transactional sends, triggered automations, drip sequences). Count every distinct send path found. For each, check whether it consults a global suppression table before dispatch. A per-campaign suppression list means an address suppressed from one campaign can still receive sends from another — this is a compliance and deliverability risk. Per-campaign-only suppression does not count as pass.
Pass criteria: A single suppression table is consulted before any email is dispatched. 100% of send paths — at least 1 — check global suppression. Count all send paths and report the ratio: "N of N send paths check global suppression." Unsubscribes, hard bounces, and complaints are suppressed globally.
Fail criteria: Suppression is per-campaign only, or the global suppression check is bypassed for 1 or more send paths (transactional sends, triggered automations).
Skip (N/A) when: Never — global suppression is a universal requirement.
Detail on fail: Example: "Suppression table scoped by campaign_id — unsubscribe from Campaign A does not prevent Campaign B from sending" or "Transactional send path does not check global suppression list before dispatch"
Remediation: Enforce a global suppression check in all send paths:
async function isSuppressed(email: string): Promise<boolean> {
const suppression = await db.suppression.findFirst({
where: { email: email.toLowerCase().trim() }
})
return suppression !== null
}
// In every send dispatch path:
async function dispatchEmail(to: string, template: string, data: object) {
if (await isSuppressed(to)) {
await db.sendLog.create({
data: { to, template, outcome: 'suppressed', suppressed_at: new Date() }
})
return // Do not send
}
await esp.send({ to, template, data })
}