Skip to content

Broadcasts

A broadcast sends one email to every contact in a segment, personalised per recipient.

This is a different path from POST /v1/send, on purpose. A single send builds one MIME message addressed to all of its recipients, which means it cannot carry a per-recipient body or a per-recipient unsubscribe link. A broadcast fans out instead: one message row and one send job per recipient.

That also means a broadcast of N recipients costs N quota units.

Lifecycle

draft ──▶ queued ──▶ sending ──▶ sent

A fan-out that fails lands in failed instead. There is no way back to draft.

Create it as a draft, then send:

bash
curl -X POST http://api.localhost/v1/broadcasts \
  -H "X-API-Key: $NOTIFYZR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "March update", "segment_id": "…", "template_id": "…" }'

curl -X POST http://api.localhost/v1/broadcasts/$ID/send \
  -H "X-API-Key: $NOTIFYZR_API_KEY"

/send returns as soon as the fan-out is queued. Nothing is mailed synchronously.

Things worth knowing

The audience is resolved when the worker runs, not when you press send. A segment is a live filter, and freezing tens of thousands of addresses into a queue message would be both enormous and already stale by the time it ran.

Suppressed addresses are excluded during fan-out, so a mostly-opted-out segment does not produce runs of empty batches.

Quota is re-checked per recipient. A long fan-out can cross the ceiling partway through. That is not a failure: what was sent was sent, the rest are counted in skipped_count, and the broadcast completes.

There is no pause. Stopping midway would leave an audience half-mailed, which is worse than either outcome.

Edit and delete are draft-only. Once queued, the content is partly out the door — and a sent broadcast's message rows would be orphaned by a delete.

Checking on one

bash
curl http://api.localhost/v1/broadcasts/$ID -H "X-API-Key: $NOTIFYZR_API_KEY"

recipient_count is the audience measured when the fan-out claimed the broadcast, queued_count how many of them actually got a message row, and skipped_count the difference — normally an opt-out or the monthly message quota running out partway.

Those three describe the fan-out. The stats object beside them describes what became of the messages it wrote, read from messages:

json
"stats": { "total": 1204, "delivered": 1187, "failed": 9, "pending": 8 }

pending covers both queued and processing, so the three outcome buckets always add up to total. Every broadcast carries stats, including a draft, where it is all zeros.

To see the individual messages, filter the message list by the broadcast:

bash
curl "http://api.localhost/v1/messages?broadcast_id=$ID" \
  -H "X-API-Key: $NOTIFYZR_API_KEY"

last_error tells you why a fan-out stopped. A failed fan-out is not retried — retrying after a partial fan-out would re-mail everyone it had already reached, so the error is recorded for an operator instead.

Released under the Apache 2.0 License.