Live demo D1 Pages Functions

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.

00:0012:0023:00

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 records

Stored 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.

📣
Dispatch
outage alert
send
Worker
consent + quiet-hours gate
check + log
🗄️
D1
ledger + audit log
Consented + daytime → sent
Quiet hours → held
Opted out → blocked

Add it to your site

D1 · gated + fully audited
Gate each send on consent + local quiet-hours
const 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';
Write an immutable audit row for every decision
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 deleted
01
Keep a consent ledger (opt-in/out per recipient and channel) in D1
02
Gate every send on consent and the recipient's local quiet-hours
03
Append one immutable audit row per decision: the regulator-evidence trail
Works with:D1Pages FunctionsAny regulated-comms workflow