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: draftsendingsent | 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.

Two surfaces

  • Public API (`/v1/broadcasts`, API-key) — create, list, get, send, and cancel broadcasts programmatically. Create + send require the sending scope; list + get require reading.
  • Dashboard API (`/broadcasts`, JWT) — the same send/cancel operations plus schedule, used by the dashboard UI.
POST/v1/broadcasts

Create a broadcast. Requires the `sending` scope. Set recipient_type to "all" to send to every contact, or "tag" with a recipient_tag to target a segment.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your API Key.

Body

namestringrequired

Internal name for the broadcast

from_emailstringrequired

Sender email — domain must be verified for sending

from_namestringoptional

Display name on the From header

subjectstringoptional

Email subject line

html_bodystringoptional

HTML body

text_bodystringoptional

Plain text body

recipient_typestringoptional

"all" (every contact) or "tag" (contacts matching recipient_tag)

recipient_tagstringoptional

Tag to target when recipient_type is "tag"

POST /v1/broadcasts
curl -X POST https://api.posthawk.dev/v1/broadcasts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key" \
  -d '{
    "name": "March Update",
    "from_email": "hello@yourdomain.com",
    "from_name": "Acme",
    "subject": "What shipped in March",
    "html_body": "<h1>This month at Acme</h1>...",
    "recipient_type": "tag",
    "recipient_tag": "newsletter"
  }'
Response
{
  "broadcast": {
    "id": "broadcast-uuid",
    "name": "March Update",
    "status": "draft",
    "from_email": "hello@yourdomain.com",
    "recipient_type": "tag",
    "recipient_tag": "newsletter"
  }
}
GET/v1/broadcasts

List broadcasts in the workspace. Requires the `reading` scope. Supports status filter and pagination.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your API Key.

Query Parameters

statusstringoptional

Filter by status: draft, sending, sent, failed, cancelled

pagenumberoptional

Page number

GET /v1/broadcasts
curl https://api.posthawk.dev/v1/broadcasts \
  -H "Authorization: Bearer your_api_key"
GET/v1/broadcasts/:id

Get a single broadcast by ID. Requires the `reading` scope.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your API Key.

Path Parameters

idstringrequired

Broadcast UUID

GET /v1/broadcasts/:id
curl https://api.posthawk.dev/v1/broadcasts/{id} \
  -H "Authorization: Bearer your_api_key"
Response
{
  "broadcast": {
    "id": "broadcast-uuid",
    "name": "March Update",
    "status": "sent",
    "from_email": "hello@yourdomain.com"
  }
}
POST/v1/broadcasts/:id/send

Send a broadcast. Requires the `sending` scope. Race-safely transitions draft → sending and fans out to contacts. Returns when fan-out completes (or is cancelled mid-send).

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your API Key.

Path Parameters

idstringrequired

Broadcast UUID

POST /v1/broadcasts/:id/send
curl -X POST https://api.posthawk.dev/v1/broadcasts/{id}/send \
  -H "Authorization: Bearer your_api_key"
Response
{
  "success": true,
  "queued": 1247,
  "message": "Broadcast sent to 1247 recipient(s)"
}
POST/v1/broadcasts/:id/cancel

Cancel an in-flight or draft broadcast. Requires the `sending` scope. Already-queued BullMQ jobs still deliver, but no further pages of contacts are fanned out.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your API Key.

Path Parameters

idstringrequired

Broadcast UUID

POST /v1/broadcasts/:id/cancel
curl -X POST https://api.posthawk.dev/v1/broadcasts/{id}/cancel \
  -H "Authorization: Bearer your_api_key"
Response
{
  "success": true,
  "status": "cancelled",
  "alreadyQueued": 423,
  "message": "Cancelled — 423 already-queued email(s) will still be delivered"
}
POST/broadcasts/:id/send

Dashboard (JWT) equivalent of POST /v1/broadcasts/:id/send. Trigger a broadcast send. Race-safely transitions from draft → sending and starts fanning out contacts.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your JWT.

Path Parameters

idstringrequired

Broadcast UUID

POST /broadcasts/:id/send
curl -X POST https://api.posthawk.dev/broadcasts/{id}/send \
  -H "Authorization: Bearer your_jwt_token"
Response
{
  "success": true,
  "queued": 1247,
  "message": "Broadcast sent to 1247 recipient(s)"
}
POST/broadcasts/:id/cancel

Dashboard (JWT) equivalent of POST /v1/broadcasts/:id/cancel. Cancel an in-flight or draft broadcast. Already-queued BullMQ jobs still deliver, but no further pages of contacts are fanned out.

Authorizations

Authorizationstring · headerrequired

Bearer authentication header of the form Bearer <token>, where <token> is your JWT.

Path Parameters

idstringrequired

Broadcast UUID

POST /broadcasts/:id/cancel
curl -X POST https://api.posthawk.dev/broadcasts/{id}/cancel \
  -H "Authorization: Bearer your_jwt_token"
Response
{
  "success": true,
  "status": "cancelled",
  "alreadyQueued": 423,
  "message": "Cancelled — 423 already-queued email(s) will still be delivered"
}