Documentation
Sign inGet started

Bulk sending: batches & broadcasts

This guide covers reaching many recipients: 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 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 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, 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. 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 applies per item, including the category default of marketing and sending by template. 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 to schedule.
Exemple de code
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).

The 202 response

A successful batch returns 202 Accepted with one entry per message, in submission order:
Exemple de code
{
  "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 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.

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). 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) into the recipient set at send time, instead of you enumerating addresses. You can already build the audiences a broadcast targets, from 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: the per-item payload in full, including fields, limits, and tags vs metadata
  • Categories: marketing vs transactional and what each does to suppression policy
  • Idempotency: key format, retention, and replay semantics
  • API reference: full batch request and response schemas