# Batch sending

This guide covers reaching many recipients at once, what most platforms call bulk email: the batch endpoint, `POST /v1/email/batches`, and where audience-targeted broadcasts fit today. If you only ever send one message at a time, start with [sending email](/docs/guides/email/sending-email) instead.

## When to use what

- **A handful of independent messages at once**: use a batch. One request carries up to 100 complete message objects and saves you 100 round trips.
- **High-volume sending from your own loop**: calling the [single-send endpoint](/docs/guides/email/sending-email) repeatedly is a sound architecture; batches don't make a message cheaper or faster to deliver, they amortize the HTTP overhead. The real volume lever is rate limits: batch requests draw from the `email_batch` [rate-limit group](/docs/guides/rate-limits#groups), separate from the `email_send` group used by single sends, so batching raises how many messages you can hand over per unit of wall-clock time.
- **One message to a stored audience**: that is a broadcast; see [Broadcasts](#broadcasts). Batches are not broadcasts: a batch has no shared content definition, no audience targeting, and no per-recipient personalization. Every message in a batch is a self-contained payload you assemble yourself.

## Batch sends

`POST /v1/email/batches` takes a JSON array of 1–100 message objects. Each item is a complete, independent send request with its own `from`, `to`, `subject`, content, and optionally its own `category`, `ip_pool_id`, tags, and metadata. The item schema is exactly the single-send payload, so everything in [sending email](/docs/guides/email/sending-email) applies per item, including the `category` default of `marketing` and sending by [template](/docs/guides/email/templates). The one exclusion is `scheduled_at`: batch items cannot be scheduled, and an item carrying it is rejected with a `422`; use the [single-send endpoint](/docs/guides/email/scheduled-sending) to schedule.

```bash
curl -X POST https://us1.platform.bird.com/v1/email/batches \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: batch-2026-07-23-001" \
  -d '[
    {
      "from": "hello@yourdomain.com",
      "to": ["delivered@messagebird.dev"],
      "subject": "Your receipt",
      "html": "<p>Thanks for your order.</p>",
      "category": "transactional"
    },
    {
      "from": "hello@yourdomain.com",
      "to": ["delivered@messagebird.dev"],
      "subject": "June product news",
      "html": "<p>What shipped this month.</p>",
      "category": "marketing"
    },
    {
      "from": "alerts@yourdomain.com",
      "to": ["delivered@messagebird.dev"],
      "subject": "Usage threshold reached",
      "text": "You have used 80% of your quota.",
      "category": "transactional"
    }
  ]'
```

### All-or-nothing validation

Every item is validated before any item is queued. If one message fails (a field-level validation error, an unverified sender domain), the entire batch is rejected with a `422` and nothing is sent. A batch never partially succeeds at accept time, so you never have to work out which half of a failed request went through; fix the offending item and resubmit the whole array. Suppression is not checked at accept time: an item whose recipients are all suppressed still accepts and gets an `em_` ID, and those recipients surface as `status: rejected` once the message is processed (see [suppressions](/docs/guides/email/suppressions)).

### The 202 response

A successful batch returns `202 Accepted` with one entry per message, in submission order:

```json
{
  "data": [
    { "id": "em_01ky7q1vmkerwa7fxyycfe5ks1", "status": "accepted", "category": "transactional" },
    { "id": "em_01ky7q1vmkesr9h1y52tkqng9f", "status": "accepted", "category": "marketing" },
    { "id": "em_01ky7q1vmkesyr1z1h4s48jjxm", "status": "accepted", "category": "transactional" }
  ]
}
```

Each child is a regular message from this point on: track it by its `em_` ID through `GET /v1/email/messages/{message_id}`, its recipient and event endpoints, and webhooks, exactly as if it had been sent individually. The same [async model](/docs/guides/email/sending-email#the-async-model-what-202-means) applies: `202` means durably accepted, and per-recipient delivery outcomes arrive afterwards.

### Idempotent retries

Send an `Idempotency-Key` header with the batch, as the example request does. If the request succeeded but you never saw the response, replaying it with the same key returns the original result, with the same child message IDs and an `Idempotency-Replay` header, instead of sending every message again. With up to 100 messages per request, the cost of an accidental duplicate is multiplied, so treat the key as required in production. See [idempotency](/docs/guides/idempotency).

### Attachments and the body cap

Each batch item can carry its own `attachments`, with the same field contract and per-message size budget as a single send (see [attachments](/docs/guides/email/attachments)). One extra limit applies at the batch level: the serialized JSON request body for the whole batch has a hard **20 MB** cap, rejected with a `413`. Base64-encoded attachments count against it, so attachment-heavy batches reach the body cap quickly; split large attachment sends across several batches, or send them individually.

## Broadcasts

A broadcast is one message sent to a stored audience: Bird resolves the audience's current members (minus [suppressions](/docs/guides/email/suppressions)) into the recipient set at send time, instead of you enumerating addresses. You can already build the [audiences](/docs/guides/email/audiences) a broadcast targets, from [contacts](/docs/guides/email/contacts) you store in your workspace, but broadcast sending itself is not yet available. The send and batch endpoints have no broadcast or audience fields, and the request body rejects unknown properties, so a broadcast cannot be addressed for now.

Until broadcast sending ships, the two options for reaching many recipients are batch sends and fanning out over the single-send endpoint from your own loop.

## Next steps

- [Sending email](/docs/guides/email/sending-email): the per-item payload in full, including fields, limits, and tags vs metadata
- [Categories](/docs/guides/email/categories): `marketing` vs `transactional` and what each does to suppression policy
- [Idempotency](/docs/guides/idempotency): key format, retention, and replay semantics
- [API reference](/docs/api/reference/create-email-message-batch): full batch request and response schemas