The Dispatch
The Midnight Caller
Regulated comms (utility outage alerts, billing, emergencies) carry consent and quiet-hours rules. The Dispatch enforces them at send time and logs every decision to an append-only audit trail, so "we only message who agreed, when allowed" is provable to a regulator, not a promise.
The pain
Emergency and outage alerts carry consent and quiet-hours rules (TCPA, state PUC). One careless blast to opted-out numbers at 2 a.m. invites fines and lawsuits.
The fix
Check consent and the recipient's local quiet-hours at send time, suppress anything non-compliant, and log every decision to an append-only D1 audit trail.
So what
Send critical notifications fast without breaking the rules, and hand a regulator a provable record of who was messaged, when, and why.
Try it
Send an outage alert.
Pick a dispatch time, set who has consented, and fire. Each recipient is gated on consent and their local quiet hours (21:00–08:00). One UTC send hour lands at different local times, so the same blast is fine for some and a violation for others.
Recipients · consent ledger
Consent is checked and quiet hours enforced server-side at send time; every decision is written to an append-only D1 audit trail below. Nothing here is asserted in a policy doc. It is enforced in the code path.
Audit trail · append-only
0 immutable recordsStored in Cloudflare D1, not your browser. and the trail is still here.
How it works
Consent and quiet-hours enforced at send time, with every decision written to an append-only audit trail.
Add it to your site
D1 · gated + fully auditedconst localHour = ((sendHourUtc + r.tz_offset) % 24 + 24) % 24;
let decision;
if (!r.opted_in) decision = 'blocked'; // no consent (TCPA)
else if (localHour < 8 || localHour >= 21) decision = 'suppressed'; // quiet hours
else decision = 'sent';await env.DISPATCH_DB.prepare(
'INSERT INTO dispatch_audit (id, batch_id, recipient_id, decision, reason, created_at) VALUES (?, ?, ?, ?, ?, ?)'
).bind(id, batchId, r.recipient_id, decision, reason, new Date().toISOString()).run();
// rows are only ever inserted, never updated or deletedOfficial Cloudflare docs