---
title: "Send to audiences"
description: "Sends one message to everybody in one or more audiences, as a separate copy for each person and personalised from each contact. Every copy has exactly one recipient and no cc or bcc, so nobody sees who else it went to, and every copy is an ordinary email with its own `msg_` id, events, tracking and webhooks. The call answers `202` straight away and the sending runs in the background, so follow it with `GET /broadcasts/{id}`."
url: "https://nextmail.sh/docs/api/broadcasts/send"
area: "API"
category: "Mailbox"
---

# Send to audiences

Sends one message to everybody in one or more audiences, as a separate copy for each person and personalised from each contact. Every copy has exactly one recipient and no cc or bcc, so nobody sees who else it went to, and every copy is an ordinary email with its own `msg_` id, events, tracking and webhooks. The call answers `202` straight away and the sending runs in the background, so follow it with `GET /broadcasts/{id}`.

`POST /broadcasts`

## POST /broadcasts

Sends one message to everybody in one or more audiences, as a separate copy for each person and personalised from each contact. Every copy has exactly one recipient and no cc or bcc, so nobody sees who else it went to, and every copy is an ordinary email with its own `msg_` id, events, tracking and webhooks. The call answers `202` straight away and the sending runs in the background, so follow it with `GET /broadcasts/{id}`.

## Example

Needs `emails:send` and `audiences:read`. `audienceIds` holds 1 to 10 ids. The body comes from `html` and or `text`, or from a stored `template`, never both, and `subject` is required unless the template supplies it.

**curl**

```
curl -X POST "$OE/broadcasts" -H "$AUTH" -H 'content-type: application/json' -d '{
  "audienceIds": ["aud_4c1b8e2a7d9f05c36b4e8a71"],
  "from": "Acme <news@acme.com>",
  "subject": "{{firstName|Hello}}, the September release is out",
  "html": "<p>Hi {{firstName|there}},</p><p>Here is what changed this month.</p>",
  "text": "Hi {{firstName|there}}, here is what changed this month. Unsubscribe: {{unsubscribeUrl}}",
  "tags": { "campaign": "release-2026-09" },
  "scheduledAt": "PT2H"
}'
```

**Response**

```
{
  "object": "broadcast",
  "id": "brd_5a8c1e3f7b2d94a06c8e1f3b",
  "status": "scheduled",
  "mode": "live",
  "source": "api",
  "audienceIds": ["aud_4c1b8e2a7d9f05c36b4e8a71"],
  "from": "Acme <news@acme.com>",
  "subject": "{{firstName|Hello}}, the September release is out",
  "counts": {
    "recipients": 412,
    "created": 0,
    "skipped": 0,
    "failedToQueue": 0,
    "queued": 0,
    "sending": 0,
    "sent": 0,
    "failed": 0,
    "cancelled": 0
  },
  "lastError": null,
  "scheduledAt": "2026-09-23T14:00:00.000Z",
  "startedAt": null,
  "completedAt": null,
  "cancelledAt": null,
  "createdAt": "2026-09-23T12:00:00.000Z",
  "updatedAt": "2026-09-23T12:00:00.000Z",
  "replayed": false
}
```

> The answer is `queued`, or `scheduled` with `scheduledAt`, which takes an ISO 8601 instant or a duration such as `PT2H`, at most 365 days out. `counts.recipients` is the estimate taken now, and the other counts start at 0. The `Location` header names the broadcast.

> Safe to retry with an `Idempotency-Key` header: the same key answers `200` with the broadcast the first call created and `Idempotency-Replayed: true`, and the same key with a different body is a `422` `idempotency_key_reuse`. Without a key, sending the same body twice sends the broadcast twice.

> The copies are not filed in the Sent folder, because the broadcast is the record. `GET /broadcasts/{id}/recipients` lists them, one per person, with what happened to each.

## Who gets it

Every contact in at least one of the audiences, counted once however many of them hold it. Two kinds of contact are left out: one that has unsubscribed, which `GET /suppressions/unsubscribes` lists, and one whose address is on the suppression list after a bounce or a complaint, or because somebody added it there. A contact added to one of the audiences after the call but before the sending reaches it is included. All contacts, the built-in audience of everyone the workspace has written to, cannot be one of the audiences.

The sending walks the audiences 50 people at a time and hands each copy to the same pipeline `POST /emails` uses, so each copy is retried, tracked and reported like any other message. `POST /broadcasts/preview` returns the count this call would start from, without sending anything.

> The whole send is checked against the monthly sends of the plan before anything is written. A broadcast the allowance cannot cover is refused with `429` `send_quota_exceeded` and leaves nothing behind. Each copy counts as one send.

## Merge fields

`subject`, `html` and `text` are filled in for each person. Every field takes a fallback after a bar, used when the contact has no value for it, so `{{firstName|there}}` becomes "there" for a contact saved without a name. Values are escaped in `html`, spaces inside the braces are allowed, and any other `{{…}}` is left exactly as written.

| Field | Filled with |
| --- | --- |
| `{{firstName}}` | The first word of the contact name. |
| `{{lastName}}` | The rest of the contact name after the first word. |
| `{{name}}` | The whole contact name. |
| `{{email}}` | The address the copy is going to. |
| `{{unsubscribeUrl}}` | The link that unsubscribes this person from the marketing mail of the workspace. |
| `{{attributes.<key>}}` | A custom field of the contact, such as `{{attributes.plan}}`. Empty, or the fallback, when the contact has no such field. |

With `template` instead of a body, the same five values are passed as props, but only the props the template declares. A template that declares `firstName` gets it, and a prop it does not declare is never sent, so the copies never fail on an unknown prop. Anything you put in `template.props` goes to every copy alike.

## Unsubscribe

Every copy carries `List-Unsubscribe` and `List-Unsubscribe-Post: List-Unsubscribe=One-Click`. That is what lets a mail client show its own unsubscribe button, and what the large mailbox providers require of bulk mail.

A body or a template that does not place `{{unsubscribeUrl}}` itself gets a short footer, "You are receiving this because you are on this mailing list. Unsubscribe", in English. Once `marketingPostalAddress` is set with `PATCH /settings`, the footer also names the sender, `marketingSenderName` or else the workspace name, and shows that address, which many countries require of marketing mail. A template receives `unsubscribeUrl` whether it declares it or not, so one that places the link decides where it goes.

The link opens a page with an Unsubscribe button, so a link scanner that fetches it unsubscribes nobody, while the one-click request a mail client makes unsubscribes straight away. Either way the person is unsubscribed from all of the marketing mail of the workspace: every audience they are in shows `unsubscribedAt` on `GET /audiences/{id}/contacts`, `GET /suppressions/unsubscribes` lists them, and later broadcasts and automation emails skip them, even after they are added to an audience again or their contact is deleted and made again. Their contact and mail sent to them one message at a time are not affected.

## A/B tests

`abTest` tries 2 to 4 versions on a share of the people first and sends the best one to everybody else. Each entry in `variants` replaces the `subject`, or the body as `html` and or `text`, and `{}` keeps the broadcast as written. A template broadcast can only vary the subject.

**curl**

```
curl -X POST "$OE/broadcasts" -H "$AUTH" -H 'content-type: application/json' -d '{
  "audienceIds": ["aud_4c1b8e2a7d9f05c36b4e8a71"],
  "from": "Acme <news@acme.com>",
  "subject": "The September release is out",
  "html": "<p>Here is what changed this month.</p>",
  "abTest": {
    "variants": [{}, { "subject": "Five things we shipped in September" }],
    "testPercent": 20,
    "metric": "opens",
    "waitMinutes": 240
  }
}'
```

`testPercent` of the people, 20 unless set, get a version, split evenly between the versions. Once `waitMinutes` have passed, 240 unless set, the version with the highest share of its people who opened, or who clicked when `metric` is `clicks`, goes to everybody else. A tie goes to the earlier version.

> `GET /broadcasts/{id}` shows where the test stands in `content.abTest.phase`, and `GET /broadcasts/{id}/stats` shows how each version did and which one won.

## Refusals

| Status | Code | When |
| --- | --- | --- |
| 403 | `from_address_forbidden` | The key may not send as `from`. |
| 404 | `audience_not_found` | An id in `audienceIds` names no audience in this workspace. |
| 409 | `domain_not_sendable` | The `from` domain cannot sign mail yet, as on `POST /emails`. |
| 422 | `audience_is_everyone` | One of the audiences is All contacts, the built-in audience of everyone the workspace has written to. |
| 422 | `no_recipients` | The audiences are empty, or everybody in them has unsubscribed or is suppressed. |
| 422 | `invalid_parameter` | No body, `html` or `text` beside `template`, no `subject` without a template, more than 10 audiences or 8 tags, or a `scheduledAt` that is not in the future or is more than 365 days out. |
| 422 | `template_not_found` | The template does not resolve. Other template refusals name `template.*` too. |
| 429 | `send_quota_exceeded` | The plan cannot cover a copy for everybody this month. |

> No attachments, cc, bcc, translation or encryption. `tags` takes up to 8, and every copy also carries `broadcast_id`, which the server adds.

## Reference

- [`POST /broadcasts`](https://nextmail.sh/docs/api/reference/broadcasts#post-broadcasts): full reference
