Fail-safe blocks sends to pending-unsubscribe contacts
Why it matters
GDPR Article 7(3) and CCPA §1798.120 require opt-outs to be honored — a race condition between async opt-out processing and a campaign send batch is not an acceptable excuse for sending to a contact who withdrew consent. CWE-284 (Improper Access Control) captures the broader class: the system fails to enforce the access control decision (do not send) during the window between request and processing. A contact who unsubscribes at 11:58 PM and a campaign batch that starts at midnight will be sent a campaign if the only guard is the processed consent record, which the async worker has not yet written.
Severity rationale
High because the race window between an opt-out request and async processing completion is predictable and exploitable by campaign send timing, creating measurable violations of GDPR Art. 7(3) and CAN-SPAM §7704(a)(4).
Remediation
Write the optOutStatus: 'pending' flag synchronously before enqueuing the job, so the pre-send guard can catch pending opt-outs even before the worker runs:
// src/app/api/unsubscribe/route.ts
export async function POST(req: Request) {
const { contactId, scope } = await req.json()
// Synchronous — takes effect immediately on all subsequent pre-send checks
await db.contact.update({
where: { id: contactId },
data: { optOutStatus: 'pending', optOutRequestedAt: new Date() }
})
await queue.add('process-optout', { contactId, scope })
return new Response(null, { status: 202 })
}
// Pre-send guard in src/lib/campaigns/can-send.ts
async function canSendCampaign(contactId: string, scope: string): Promise<boolean> {
const contact = await db.contact.findUnique({ where: { id: contactId } })
if (contact?.optOutStatus === 'pending' || contact?.optOutStatus === 'completed') return false
return hasConsent(contactId, scope)
}
The synchronous status write costs one extra database round-trip but eliminates the race window entirely.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Compliance & Consent Engine Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- gdpr · Art. 7(3) — Processing must cease upon withdrawal of consent
- cwe · CWE-284 — Improper access control — pending-unsubscribe contact remains reachable
- ccpa · §1798.120 — Business must stop selling/sharing upon opt-out
- external · CAN-SPAM-§7704(a)(4) — CAN-SPAM Act — prohibits sending during opt-out processing window
Taxons
History
- 2026-04-18·v1.0.0·Initial import from compliance-consent-engine·automated