Broadcasts
Compose, schedule, and send one email to many contacts.
A broadcast is one email sent to many contacts: all of them, a segment, or a topic. Compose in the dashboard's block editor (with per-contact merge fields) or create broadcasts via the API.
Lifecycle
draft → scheduled → sending → sent
↘ canceled- Broadcasts are created as drafts. Only drafts can be edited or deleted.
POST /broadcasts/{id}/sendschedules the send — immediately, or at ascheduled_attimestamp.- A queued broadcast — scheduled, or already going out — can be canceled
with
POST /broadcasts/{id}/cancel. Emails already sent are not recalled; the response'scanceled_remainingsays how many were stopped, andsent_counton a read says how many had gone out. - On the wire,
scheduledandsendingboth read asqueued(matching the Resend SDK's status union);canceledis a MillionSend extension. A broadcast readsqueuedwithsent_atnull until its last email has gone out, however long that takes.
What the fan-out does
Every recipient email goes through the same pipeline as a transactional send, plus broadcast-specific handling:
- Audience resolution — globally unsubscribed contacts are always excluded; segment filters and topic subscriptions are evaluated at send time.
- Suppression checks — addresses on the suppression list are skipped.
- Merge fields — per-contact values (name, custom properties) are substituted into the template.
- Unsubscribe links — RFC 8058 one-click
List-Unsubscribeheaders and a hosted unsubscribe link are added to every message. To place the link in the body yourself, write{{{UNSUBSCRIBE_URL}}}— it is replaced per-recipient with their hosted unsubscribe URL.{{{RESEND_UNSUBSCRIBE_URL}}}is a supported alias, so templates written for Resend keep working unchanged (and roll back unchanged).
Broadcast emails enter the send queue below transactional ones: a transactional email accepted while a broadcast is going out is sent ahead of the remaining recipients. Sends run on several lanes at once, paced by the instance's SES send rate.
On a self-hosted instance, unsubscribe links are built from APP_BASE_URL,
so broadcast sending is rejected until it is configured — see
Self-hosting. On Cloud this is
automatic.
Pacing
The platform sends a bounded number of emails a day. A broadcast that fits inside what is available goes out at once, at the sending rate. A larger one goes out in waves: the first wave now, the rest as capacity frees over the following days. Transactional email is never held behind a broadcast.
The send tells you up front:
{
"id": "8c1f0b8e-…",
"finishes_at": "2026-09-18T13:26:05Z",
"estimated": true,
"warning": {
"code": "paced",
"days": 3,
"message": "170,000 recipients exceed the broadcast capacity available now; sending is paced and finishes about 2026-09-18T13:30:00Z. Transactional email is unaffected."
}
}finishes_atis the estimated instant the last email goes out;nullwhen no estimate is available. It is an estimate: other sends move it.warningis present only when the send takes more than one wave —pacedwhen the audience exceeds the capacity available now,queued_behindwhen other sends are ahead (the message says when this one starts).- While a broadcast is going out,
GET /broadcasts/{id}and the list carry a livefinishes_atandsent_count. - An audience that would need more than 24 days of capacity — the
platform's, or your plan's — is refused with
422 broadcast_too_largeinstead of accepted and left waiting. Split it into smaller segments or contact support.
Guardrails
- Only a verified domain of your team may appear in
from. - If your trailing bounce or complaint rate has crossed the SES pause
threshold, new broadcast sends are blocked with a
403 sending_pausederror — stopping the damage before SES pauses sending entirely. - If the platform's aggregate bounce or complaint rate in your sender's SES
region approaches the SES review line, broadcast sends in that region are
refused with
403 broadcasts_pauseduntil the rate recovers. Transactional email keeps flowing, and the pause clears on its own.
See the API reference for all broadcast endpoints.
Recent changes
- A broadcast with emails still waiting — for capacity, or for the plan's
cap to reset — reads
queuedwithsent_atnull until its last email has gone out. It used to readsentas soon as every email was written. - An audience whose plan cap would need more than 24 days is refused with
422 broadcast_too_largeinstead of accepted and left waiting. POST /broadcasts/{id}/cancelworks on a broadcast that is already going out; the response carriescanceled_remaining.