# Broadcasts > Draft an email to your audience, send a test to your team, then prepare and send it, all at once or in batches, and read back who got it. Source: https://getlaunchlist.com/help/docs/api/broadcasts --- A broadcast is one email to an audience in a project: the same thing the dashboard's Emails → Broadcasts tab composes. The API drafts it, sends a test to your team, and then sends it in two steps so that what goes out is exactly what you looked at. ## Scopes | Operation | Scope | | -------------------------------------------------- | ------------------ | | List, retrieve, recipients | `broadcasts:read` | | Create, update, delete a draft, send a test | `broadcasts:write` | | Prepare, send, schedule, pause, resume, cancel | `broadcasts:send` | ## Create a draft ``` POST /v1/broadcasts ``` | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------- | | `project` | string | Yes | A `prj_` handle. | | `name` | string | No | What the dashboard lists it as. Defaults to the subject. | | `subject` | string | No | Up to 200 characters. Required before it can be sent. | | `preheader` | string | No | The preview line most inboxes show after the subject. | | `from_name` | string | No | Defaults to the project's saved sender. | | `reply_to` | string | No | Defaults to the project's saved reply-to address, then to the workspace owner's email. | | `content` | object | No | `{ "format", "body" }`, below. | | `audience` | object | No | Who gets it, below. Defaults to everyone subscribed, narrowed by the project's default `recipients`. | | `delivery` | object | No | All at once (default) or in batches, below. | Returns **201** with the [broadcast object](#the-broadcast-object). A draft never sends anything. ### Content `format` is one of: - `text`: a plain-text email, no HTML and no tracking. It reads like a note from a person, which is what you want for founder updates and questions. - `markdown`: formatted, and rendered with the project's email design. - `html`: sent as you wrote it. Merge tags work in the subject and body, for example `{{first_name|there}}`, where the part after `|` is the fallback when a name is missing or is clearly not a name. Every email gets an unsubscribe link and your postal address automatically. ### Audience ```json { "type": "segment", "recipients": "verified", "segment": "seg_7qk2mn4pr8vx", "exclude": { "tags": ["tag_3hx8kq2mvp4r"], "segments": ["seg_2pv9kd4mxq8r"], "broadcasts": ["bcd_9rk2mx4pq7vn"] } } ``` `type` is `all`, `segment` (with `segment`) or `tag` (with `tag`). `recipients` chooses, among the people the audience could reach, `verified` (they clicked the verification link), `unverified` (waiting for verification: sent a link to verify their email, an expired one included, and not clicked it yet) or `all`. Someone who was never asked to verify (a form that does not ask, `POST /v1/contacts`, an import) is neither verified nor waiting, so only `all` reaches them. Everyone who joins is subscribed, so this only narrows who is emailed, never widens it. Leave it out on a new broadcast and it takes the project's default: `verified` when every active form asks people to verify their email, otherwise `all`. On an update, an `audience` sent without `recipients` keeps the broadcast's own choice, so replacing the audience never widens who it goes to. `exclude` leaves out anyone in the listed segments or tags, or anyone who received one of the listed broadcasts. Every value is a handle from this project: one from another project is a **422** naming it, never ignored, because ignoring an exclusion would widen the send. ## Update or delete a draft ``` PATCH /v1/broadcasts/{broadcast} DELETE /v1/broadcasts/{broadcast} ``` `PATCH` changes only the fields you send. A broadcast can be edited while it is `draft`, `scheduled` or `failed`; after that it is a **409**. Only a `draft` or `failed` broadcast can be deleted: one that went out stays as the record of who received it. ## Send a test ``` POST /v1/broadcasts/{broadcast}/test ``` Sends one `[TEST]` copy to `email`, which must belong to a member of your workspace. Merge tags are filled with sample values. ## Prepare, then send ``` POST /v1/broadcasts/{broadcast}/prepare POST /v1/broadcasts/{broadcast}/send ``` `prepare` runs the checks the dashboard runs before a send and writes nothing. It returns: ```json { "object": "broadcast_preparation", "broadcast": "bcd_4kq8mx2rvn7p", "ready": true, "problems": [], "recipients": 214, "first_batch": 50, "delivery": { "mode": "batches", "batch_size": 50, "interval": "day" }, "gradual": null, "recipient_choice": "verified", "unverified_recipients": 0, "skipped": { "audience": 240, "unsubscribed": 9, "cannot_receive": 3, "not_subscribed": 4, "not_chosen": 8, "excluded": 2, "shared_address": 0 }, "send_at": null, "name_fallback": { "first_name": { "count": 31, "capped": false } }, "quota": { "limit": 10000, "used": 1200, "remaining": 8800 }, "extra_emails": null, "sample": [{ "id": "con_7kq3mxrz9dnp", "identifier": "ad***@example.com", "name": "Ada" }], "confirmation_token": "eyJiIjoi...", "tolerance": 2, "expires_at": "2026-09-24T10:17:11+00:00" } ``` `problems` lists anything that would stop the send, in plain words. `skipped` explains who in the audience will not get it (`audience` is `recipients` plus the other six): `unsubscribed`, `cannot_receive` (a bounce, a spam report, a block or an address that cannot receive email), `not_subscribed` (an imported contact added as not subscribed), `not_chosen` (left out by `audience.recipients`), `excluded` and `shared_address` (two contacts with the same address get one email). `recipient_choice` is the choice the send uses and `unverified_recipients` is how many of `recipients` have not verified their email: show the founder that number before asking for a yes. `gradual` is `null`, or says the send goes out gradually because the sender is new: `first_batch`, `hourly_limit`, `batches`, `hours` and a `message` you can show as it is. When it is set, `first_batch` is the first step of that ramp, whatever `delivery` says. `name_fallback` counts recipients greeted by the fallback name; on a list over 10,000 people it counts the first 10,000 by signup and says `capped: true`. `skipped` and `name_fallback` are reused for up to two minutes while the broadcast is unchanged, so on a list that is still growing they can trail `recipients` slightly. `extra_emails` is `null` while the plan covers the send. When it does not, it says how many emails the send is short, the extra emails that would cover it and their price, and links to the dashboard, where the founder approves them: the API never buys them. To schedule rather than send now, pass `send_at` to `prepare` too: the token is bound to the time it was prepared for, so a token prepared for now cannot schedule, and one prepared for a time cannot send now. `send` sends now and answers **202** with the broadcast (`status` is `queuing` or `sending`: on its way, not yet delivered). With `send_at` (ISO 8601; without an offset it is read as UTC) it is scheduled instead and answers **200**. Pass the `confirmation_token` from `prepare` and the send only happens if nothing changed since: same content, same audience (including the rules of any segment it names), the same status, the same `send_at`, prepared by the same key, within 15 minutes, and the same number of recipients within `tolerance`: exact for a list under 100 people, within 1% above that, so a list that keeps growing while you read the numbers does not refuse forever. For a batched broadcast that is the whole audience, not just the first batch. Otherwise it is a **422** on `confirmation_token` that says what changed, and nothing is sent. The token is optional over REST, for an integration with its own review step; over MCP it is required. ## Send a broadcast in batches Set `delivery` to send to a slice of the audience at a time: ```json { "mode": "batches", "batch_size": 50, "interval": "day" } ``` The first batch goes out when you send; after that, one batch of `batch_size` people every `hour` or `day`, oldest signups first, until everyone has it. The audience is read live at each batch, so someone who signs up mid-send joins a later batch, and nobody ever gets it twice. `stats.batches` shows how many batches went out, how many people are left and when the next one is due. If a batch bounces or draws complaints past a safe rate, the broadcast pauses itself and `failure_reason` says why. Fix the list, then resume it. ## Pause, resume, cancel ``` POST /v1/broadcasts/{broadcast}/pause POST /v1/broadcasts/{broadcast}/resume POST /v1/broadcasts/{broadcast}/cancel ``` Each answers with the broadcast in its new state, or a **409** naming its current state when there was nothing to do. Pausing keeps a batched broadcast's schedule; resuming sends to everyone who has not had it yet; canceling is final. Emails already sent stand. ## List broadcasts and recipients ``` GET /v1/broadcasts?project=prj_…&status=sent GET /v1/broadcasts/{broadcast}/recipients?status=bounced ``` Both are [paginated](/help/docs/api/pagination). A recipient carries the contact's `con_…` handle, its address and a `status` of `queued`, `sent`, `delivered`, `bounced`, `complained`, `suppressed`, `failed` or `canceled`, the same values a [contact's email history](/help/docs/api/contacts-emails) uses. `canceled` means the broadcast was canceled before it reached them. In a list, `stats.batches.remaining` is null: it is a live count of the audience, so it is only computed when you retrieve one broadcast. ## A follow-up that skips people who replied 1. Save a segment of people who got the first broadcast at least five days ago: `{ "field": "received_broadcast", "operator": "in", "value": ["bcd_4kq8mx2rvn7p"], "days": 5 }` ([segments](/help/docs/api/segments)). 2. Create the follow-up with that segment as its audience, excluding a `replied` tag and a segment of people who already joined. 3. Prepare it, check the count, and send. ## The broadcast object ```json { "object": "broadcast", "id": "bcd_4kq8mx2rvn7p", "project": "prj_7Qk2mN4pR8", "name": "Launch week", "status": "sent", "subject": "We launch Tuesday", "preheader": null, "from_name": "Ada from Acme", "reply_to": "ada@acme.com", "content": { "format": "markdown", "body": "Hi {{first_name|there}} ..." }, "audience": { "type": "all", "recipients": "all", "segment": null, "tag": null, "exclude": { "segments": [], "tags": [], "broadcasts": [] } }, "delivery": { "mode": "all", "batch_size": null, "interval": null }, "scheduled_at": null, "sent_at": "2026-09-24T10:20:00+00:00", "failure_reason": null, "stats": { "recipients": 214, "sent": 214, "delivered": 209, "bounced": 2, "complained": 0, "unsubscribed": 3, "pending": 0, "failed": 0, "suppressed": 0, "canceled": 0, "batches": null }, "created_at": "2026-09-24T10:02:11+00:00", "updated_at": "2026-09-24T10:20:00+00:00", "url": "/v1/broadcasts/bcd_4kq8mx2rvn7p" } ``` ## Next - [Segments](/help/docs/api/segments), to build the audience a broadcast targets - [Tags](/help/docs/api/tags), to label the people a broadcast should reach or skip ## Common questions ### Which scopes do broadcasts need? broadcasts:read to list them, read results and recipients. broadcasts:write to create and edit drafts and send a test to your team. broadcasts:send to prepare, send, schedule, pause, resume and cancel. Only a teammate who may send campaigns can create a key with broadcasts:send. ### Can a broadcast reach someone who unsubscribed? No. Only subscribed contacts are ever emailed. Unsubscribed, bounced, complained and not-subscribed contacts are skipped whatever the audience says, and prepare tells you how many. ### Can I email people who have not verified their email? Yes. Everyone who joins is subscribed, so they can be emailed whether or not they verified. Set audience.recipients to verified, unverified or all. A new broadcast takes the project default: verified when every active form asks people to verify, otherwise all. ### Why does prepare say my first broadcast goes out gradually? Your first emails go out gradually. Until your sending record is established, a broadcast to more than 50 people starts with 50 and grows each hour, up to 1,000 an hour. prepare returns gradual with the first batch, the hourly limit and a plain sentence you can show the founder. ### Can one person get the same broadcast twice? No. Each broadcast writes one ledger row per person and the database refuses a second. Batches, retries, a resume after a pause and a send that is called twice all land on that same row. ### Why is my confirmation token refused? A token lasts 15 minutes and only while nothing changed: the subject, sender, content, audience, delivery and status must match, send_at must be the one it was prepared with (or absent for sending now), the recipient count must not have moved past its tolerance (exact under 100 people, within 1% above that), and it must be spent by the key that prepared it. Call prepare again and look at the new numbers. ### Are opens and clicks reported? Not yet. Tracking is off, so the API reports sent, delivered, bounced, complained and unsubscribed, and never a zero that looks like nobody opened. ### Can I write a designed block email over the API? No. Block emails are designed in the dashboard composer. The API reads them as `format: blocks` with a null body and can send them, but it writes `markdown`, `text` or `html`.