Broadcasts

One-off marketing campaigns to your contacts. Race-safe state machine with mid-send cancellation, send-time unsubscribe re-check, and live progress polling.

Broadcasts let you send a one-off email to all contacts (or contacts matching a tag) without setting up a recurring newsletter. They are managed via the dashboard, but the worker exposes a JWT-authenticated API for cancel and send operations.

State machine: `draft` → `sending` → `sent` | `failed` | `cancelled`

Production-hardening features:

• **Race-safe transitions** — every state-changing UPDATE includes the expected current status in the WHERE clause (e.g. `WHERE status='draft'`), so a concurrent cancel that lands in the gap between page activations always wins.
• **Send-time unsubscribe re-check** — before each individual SES handoff, the worker re-reads the contact's `unsubscribed` field. Contacts who unsubscribed after the initial page query but before queue insertion are skipped.
• **Audit log** — every state change is appended to `broadcasts.audit_log` (jsonb) with the action, timestamp, and details for debugging.
• **Cancel mid-send** — the fan-out loop checks status before each page of contacts, stopping cleanly. Already-queued BullMQ jobs still deliver (we don't reach into the queue), but no further pages fan out.
• **Live progress polling** — the dashboard polls every 3 seconds while a broadcast is `sending`, showing a live `sent / total` count and progress bar.

## Throughput

Broadcast fan-out shares the same SES rate limit as everything else: **AWS SES caps the account at 14 emails/sec** and the Posthawk worker rate-limits dispatch to **12 emails/sec** to leave headroom. So a broadcast to 1,000 contacts takes ~83 seconds to fully ship, 10,000 contacts ~14 minutes. The dashboard's live progress bar reflects the actual drain rate.

API endpoints (JWT-authenticated, called from the dashboard):
POST/broadcasts/:id/sendJWT

Trigger a broadcast send. Race-safely transitions from draft → sending and starts fanning out contacts. Returns when fan-out completes (or is cancelled).

ParameterTypeInDescription
idrequiredstringpathBroadcast UUID

Response

json
{
  "success": true,
  "queued": 1247,
  "message": "Broadcast sent to 1247 recipients"
}
POST/broadcasts/:id/cancelJWT

Cancel an in-flight or draft broadcast. Race-safe: only flips a row in draft or sending state. Already-queued BullMQ jobs still deliver, but no further pages of contacts are fanned out.

ParameterTypeInDescription
idrequiredstringpathBroadcast UUID

Response

json
{
  "success": true,
  "status": "cancelled",
  "alreadyQueued": 423,
  "message": "Cancelled — 423 already-queued emails will still be delivered"
}