Opt-out processed within regulatory window
Why it matters
CAN-SPAM §7704(a)(4) gives you 10 business days to honor an opt-out request — but without a processed_at timestamp, you cannot prove you met that window for any specific request. GDPR Article 7(3) requires withdrawal to be honored without delay. CCPA §1798.120 imposes similar obligations. CWE-778 (Insufficient Logging) applies: if opt-out processing timing is not measured and recorded, you have no way to detect when a queue backlog is pushing processing into violation territory, and no evidence of compliance when a regulator asks. The absence of timestamps turns a solvable reliability problem into an undetectable and undefendable one.
Severity rationale
Critical because without `processed_at` timestamps and queue depth alerting, the system cannot demonstrate CAN-SPAM §7704(a)(4) compliance for any specific opt-out request, and queue failures accumulate silently until a regulator asks for evidence.
Remediation
Add processed_at and processing_ms fields to opt-out records and write them at worker completion. Set a queue monitoring alert at 2 hours — well inside the 10-business-day CAN-SPAM ceiling:
// In your opt-out worker (src/workers/optout.ts)
queue.process('process-optout', async (job) => {
const { contactId, scope, requestedAt, recordId } = job.data
// ... suppression cascade ...
await db.optOutRecord.update({
where: { id: recordId },
data: {
processedAt: new Date(),
processingMs: Date.now() - new Date(requestedAt).getTime()
}
})
})
Add a queue depth alert in your monitoring stack (Datadog, Grafana, BullMQ's getWaiting()) that fires if any opt-out job is older than 2 hours without processing. Document the alert threshold alongside the retention constants in src/lib/compliance/constants.ts.
Detection
-
ID:
regulatory-window -
Severity:
critical -
What to look for: CAN-SPAM requires unsubscribe requests to be honored within 10 business days. This check verifies that the system has a measurable, monitored processing window. Look for: SLA monitoring on the opt-out queue (alerts if jobs sit unprocessed beyond a threshold), a
processed_attimestamp on opt-out records (to measure actual processing time), and monitoring/alerting for queue backlog. Also check if the fail-safe (below) prevents sends during the processing window regardless of processing speed. -
Pass criteria: The opt-out pipeline records a
processed_attimestamp. The system has queue monitoring that alerts if jobs exceed a processing threshold (e.g., no more than 1 hour). The fail-safe blocks sends to contacts inpending-unsubscribestate regardless of processing speed. Count all timestamp fields on the opt-out record — at least 2 must exist (requested_at and processed_at). -
Fail criteria: No
processed_attimestamp on opt-out records — there is no way to measure actual processing time. No queue monitoring or alerting. No fail-safe to prevent sends during processing delay. -
Skip (N/A) when: No email sending; or the application relies entirely on the ESP's own unsubscribe mechanism with no custom opt-out processing.
-
Detail on fail:
"Opt-out jobs have no processed_at timestamp — compliance window cannot be measured or demonstrated"or"No queue depth monitoring — if workers crash, opt-outs silently accumulate with no alert" -
Remediation: Record timestamps and add queue monitoring:
// Record both requested_at and processed_at interface OptOutRecord { contactId: string scope: string requestedAt: Date // when the user clicked unsubscribe processedAt?: Date // when the system completed suppression cascade processingMs?: number } // In your worker, after completing all steps: await db.optOutRecord.update({ where: { id: job.data.recordId }, data: { processedAt: new Date(), processingMs: Date.now() - new Date(job.data.requestedAt).getTime() } })Set a queue depth alert: if any job is older than 2 hours without processing, fire an alert. Keep this threshold well within the 10-business-day CAN-SPAM window.
External references
- ccpa · §1798.120 — Opt-out must be honoured within required timeframe
- gdpr · Art. 7(3) — Withdrawal shall not affect lawfulness of prior processing
- external · CAN-SPAM-§7704(a)(4) — CAN-SPAM Act — unsubscribe must be honoured within 10 business days
- cwe · CWE-778 — Insufficient logging — no processed_at timestamp means SLA cannot be measured
Taxons
History
- 2026-04-18·v1.0.0·Initial import from compliance-consent-engine·automated