# Send a broadcast to a segment

> Marketing sends to a segment or topic: drafts with blocks or HTML, consent at send time, scheduling, per-domain throttling, subject A/B tests, test sends, progress, analytics and cancel.

Source: https://docs.minket.com/broadcasts/
Updated: 2026-10-29

A broadcast is one marketing email to an audience: a segment, a topic, or a segment narrowed to a topic's subscribers. You write a draft, send a test, then send it now or at a time. The platform takes a snapshot of who may receive it, sends one email per contact through the normal sending pipeline, and keeps count of what happened.

## A draft

```sh tab=curl
curl https://api.minket.com/v1/broadcasts \
  -H "Authorization: Bearer $MINKET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October update",
    "from": "Acme <news@news.acme.example>",
    "reply_to": "hello@acme.example",
    "subject": "What changed in October, {{first_name}}",
    "preview_text": "Three new features and one fix.",
    "segment_id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5d",
    "topic_id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
    "document": {
      "version": 1,
      "settings": { "footer": "Acme Ltd, 1 Example Street, Example City" },
      "blocks": [
        { "type": "heading", "level": "1", "text": "Hello {{first_name}}" },
        { "type": "paragraph", "text": "Here is what changed on your **{{plan}}** plan." }
      ]
    }
  }'
```

```ts tab
const draft = await minket.broadcasts.create({
  name: "October update",
  from: "Acme <news@news.acme.example>",
  subject: "What changed in October, {{first_name}}",
  segment_id: "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5d",
  html,
});
await minket.broadcasts.test(draft.id, { to: ["me@acme.example"] });
await minket.broadcasts.send(draft.id, { scheduled_at: "tomorrow at 9am" });
```

- Content is a [block document](https://docs.minket.com/templates/#block-documents) or `html` (plus an optional `text` part); new content of one kind replaces the other. Variables are the contact's fields and properties, escaped as in [templates](https://docs.minket.com/templates/#variables).
- Every email ends with an unsubscribe link and a preferences link, signed for that contact (with the broadcast's topic, the unsubscribe link leaves only that topic). If the content places `{{unsubscribe_url}}` itself, only the preferences link is added.
- `from` must be on a verified marketing domain of the project; a transactional domain is refused with `stream_rule_violation`. Marketing content checks apply.
- `PATCH` changes a draft; `DELETE` removes a draft, a canceled or failed broadcast, or a scheduled one (it is canceled first).
- `POST /v1/broadcasts/{id}/duplicate` copies any broadcast, a sent one included, into a new draft named `<name> (copy)`: sender, reply-to, subject, preview text, audience, content, throttle and the A/B test's variants and settings (not its winner). It answers `201` with the draft; nothing about the original's sending carries over. To change a broadcast that is no longer a draft, duplicate it.

## Who gets it

When sending starts, the audience is resolved to a snapshot (`GET /v1/broadcasts/{id}/recipients`) of the contacts who may receive marketing mail right then: subscribed (not unsubscribed), `active` (not bounced, complained or sunset), opted in to the topic (their own choice, else the topic's default) and with no marketing suppression in force. The console shows the same count live while you pick the audience.

Consent and suppression are checked again for each recipient when its email is created, and the send service checks suppressions, the topic and the kill switch once more. A contact who unsubscribed or was suppressed after the snapshot is `skipped` with a reason (`unsubscribed`, `suppressed`, `topic_opt_out`, the contact's status) and gets nothing.

Each email is a normal email (`source: "broadcast"`, with `broadcast_id`, `contact_id` and `topic_id`), with its own timeline, events and webhooks. A recipient is marked `sent` in the same transaction that creates its email, so a step that runs twice never sends anyone twice.

## Sending, scheduling and throttling

- `POST /v1/broadcasts/{id}/send` answers `202 { "object": "broadcast", "id", "status" }`. Without `scheduled_at` (as for [emails](https://docs.minket.com/sending/#scheduling), up to 30 days ahead) it starts now. Sending a scheduled broadcast again moves it to the new time.
- `POST /v1/broadcasts/{id}/cancel` stops a scheduled, sending or paused broadcast at once: nothing more is sent, what was sent stays sent, and it cannot be resumed. Its emails that SES has not taken yet (held by the kill switch, or still queued) are canceled when the send worker reaches them, with an `email.canceled` event each, and their recipients are `skipped` with the reason `canceled`.
- `status` moves through `draft`, `scheduled`, `queued` (the snapshot is being taken), `testing` (an A/B sample is out), `sending`, then `sent`. `paused`, `canceled` and `failed` are the other ends. `broadcast.started` and `broadcast.completed` events mark the edges.

Broadcasts are throttled per sending domain. Each one sends at most its `throttle_per_minute` (600 by default, 1 to 10,000), and every broadcast from the same domain draws on one per-minute budget. That budget never goes above half of the platform's sending rate, so transactional mail always has room.

### Pauses

A broadcast pauses where it stopped, with the reason in `error`, when:

- the project's [kill switch](https://docs.minket.com/deliverability/#when-sending-pauses) is on: clear it, then send the broadcast again to resume;
- the project reached its daily sending cap: it resumes by itself at midnight UTC, or raise the cap and send it again;
- its domain stopped passing (not verified, authentication failing, sending turned off): fix the domain, then send it again.

Resuming carries on with the same snapshot; recipients already sent are never sent again.

## A/B tests

```json
{
  "ab_test": {
    "variants": [
      { "key": "a", "subject": "What changed in October" },
      { "key": "b", "subject": "Three new features" }
    ],
    "sample_percent": 20,
    "window_minutes": 120,
    "metric": "opens"
  }
}
```

`sample_percent` (5 to 50) of the snapshot is picked at random and split evenly between the two subjects. The sample's emails are tracked for the metric whatever the domain's tracking setting. Once the sample is out, the test window (`window_minutes`, 15 minutes to 24 hours) starts; when it ends, the subject with more unique opens (or clicks) per email sent wins (a tie goes to `a`), `ab_test.winner` and `ab_test.results` are set, and everyone else gets the winning subject. Opens flatter whichever variant reached more Apple Mail users ([why](https://docs.minket.com/delivery/#open-and-click-tracking)), so choose `clicks` when the email has a link worth clicking.

## Test sends

`POST /v1/broadcasts/{id}/test` with `{ "to": ["me@acme.example"] }` sends the current content to up to five addresses, each as its own email with `[Test]` before the subject. Test copies are not part of the broadcast: they are not counted and do not change its status. An address that is a contact gets its own variables and working links; any other address gets links that open the recipient pages without changing anything.

## Progress and analytics

`GET /v1/broadcasts/{id}` carries `stats`:

| Field | What it counts |
|---|---|
| `recipients` | Contacts in the snapshot. |
| `sent`, `pending`, `skipped`, `failed` | Recipients whose email was created, who are still waiting, who were left out at send time, or whose email could not be created. |
| `delivered`, `opened`, `clicked`, `bounced`, `complained` | The broadcast's emails with at least one such event (each email once). |
| `unsubscribed` | Recipients who unsubscribed from marketing mail, or from the broadcast's topic, after their email went out. |

The counts follow the broadcast's emails as their events arrive. Once a broadcast has finished, a copy of its counts is kept with it, so they survive the recipient list's 13-month retention.

`GET /v1/broadcasts/{id}/recipients` lists the snapshot, newest contact first, with each recipient's `status`, `email_id` and `sent_at`. Filter it by `status`, by part of the address (`email`, any case), or by what happened to the email with `event`:

| `event` | Recipients | Adds |
|---|---|---|
| `sent`, `delivered`, `complained` | whose email had that event | |
| `opened` | whose email was opened | `count`, opens (once per day) |
| `clicked` | who clicked a link | `count` and `clicked_links` (`url`, `clicks`), clicks once per link and day |
| `bounced` | whose email bounced; `bounce_type` narrows it to `permanent`, `transient` or `undetermined` | `bounce_type` |
| `unsubscribed` | who unsubscribed after it went out, as `stats.unsubscribed` counts | |
| `suppressed` | skipped at send time, or refused at delivery, because the address was suppressed | |

`GET /v1/broadcasts/{id}/links` lists the links recipients clicked, most clicked first: `url`, `clicks` (each recipient once per link and day) and `unique_clicks` (recipients). Each row's `id` is a cursor for `after` and `before`; it names no object.

## Test keys

A test key's broadcasts send [test-mode](https://docs.minket.com/sending/#test-mode) emails, which appear in the sandbox. Test keys see only test-mode broadcasts and live keys only live ones; the console shows both, marked.
