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.
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.
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.
ID: compliance-consent-engine.opt-out-processing.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_at timestamp 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_at timestamp. 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 in pending-unsubscribe state 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_at timestamp 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.