Documentation
Sign inGet started

Batch sending

POST /v1/email/batches takes up to 100 complete send payloads in one request and queues them together. Each item is an independent message with its own sender, recipients, and content, so a batch is how you hand over a pile of unrelated mail, receipts, alerts, per-user notifications, in one call instead of 100. If you only ever send one message at a time, sending email is the page you want.

When to use what

  • Independent messages you already have in hand. Use a batch and hand them over in one request.
  • A steady high-volume stream. Calling the single-send endpoint in a loop is a sound architecture, and a batch makes no single message cheaper or quicker to deliver. What it changes is throughput, because batch requests draw on the email_batch rate-limit group rather than the email_send group, and each one takes up to 100 messages.
  • One email to a stored audience. That is a broadcast, which resolves the audience into recipients and personalizes per contact.

Batch sends

The request body is a JSON array of 1 to 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 the single-send payload exactly, so everything in sending email applies per item, including the category default of marketing and sending by template.
One field is excluded. A batch item cannot have scheduled_at, and an item that does is rejected with a 422. To schedule a message, send that one on its own with scheduled sending.
const batch = await bird.email.sendBatch([
  {
    from: { email: "onboarding@messagebird.dev", name: "Bird" },
    to: ["alice@example.com"],
    subject: "Your receipt",
    html: "<p>Thanks, Alice.</p>",
  },
  {
    from: { email: "onboarding@messagebird.dev", name: "Bird" },
    to: ["bob@example.com"],
    subject: "Your receipt",
    html: "<p>Thanks, Bob.</p>",
  },
]);
for (const item of batch.data) console.log(item.id, item.status);

All-or-nothing validation

Every item is validated before any item is queued. If one message fails, a field-level validation error or an unverified sender domain, the whole batch is rejected with a 422 and nothing is sent: fix that item and resubmit the array.
Suppression is not part of that check. An item whose recipients are all suppressed still accepts and gets its own em_ ID, and those recipients come back 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:
Codebeispiel
{
  "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 an ordinary message: track it by its em_ ID through GET /v1/email/messages/{message_id}, its recipient and event endpoints, and webhooks, exactly as if you had sent it on its own. The same async model applies, so 202 means durably accepted and per-recipient 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, the same child message IDs and an Idempotency-Replay header, instead of sending every message again. One accidental duplicate here costs you up to 100 emails, so treat the key as required in production. See idempotency.

Attachments and the body cap

Each batch item can have its own attachments, on the same field contract and per-message size budget as a single send (see attachments). One more limit applies to the batch as a whole: the serialized JSON request body is capped at 20 MB, and a larger body is rejected with a 413. Base64-encoded attachments count against it, so attachment-heavy batches reach the cap quickly. Split them across several batches, or send them one at a time.

Broadcasts

A broadcast sends one email to a stored audience. We resolve the audience's current members, minus suppressions, into the recipient list when the send starts, and each recipient's contact properties fill the template's variables. Drive one from the dashboard, from /v1/email/broadcasts, or with the bird email broadcasts commands. Broadcasts has the walkthrough.

Next steps

  • Sending email: the per-item payload in full, including fields, limits, and tags vs metadata
  • Broadcasts: one email to a stored audience, personalized per contact
  • Categories: marketing and transactional, and what each does to suppression policy
  • Idempotency: key format, retention, and replay semantics
  • API reference: the full batch request and response schemas