Send up to 100 transactional emails in a single request. Each item is metered, sent, and logged independently, so one bad item fails on its own while the rest still send. For a single email, use Send email.
| Header | Value |
|---|---|
Authorization | Bearer ps_live_YOUR_API_KEY — required |
Content-Type | application/json — required |
A JSON object with a single emails field: a non-empty array of 1 to 100 items. An empty array, a non-array, or more than 100 items is rejected as a whole-batch 400 invalid_field (param: "emails").
Each item has the same shape as a single send:
| Field | Type | Required | Description |
|---|---|---|---|
to | string | required | Recipient email address. |
subject | string | required | Email subject line. |
html | string | required | HTML body of the email. |
sender_id | string | optional | Per-item sender id (Settings → Senders). Takes precedence over from; a missing/cross-tenant id is a per-item sender_not_found, an unverified-domain id a per-item sender_not_verified. |
from | string | optional | Per-item sender address on a verified domain. Ignored when sender_id is given. Omit both to use your workspace default sender. |
reply_to | string | string[] | optional | Per-item Reply-To — a single email address or an array. Precedence: this field, then the resolved sender's default reply_to (Settings → Senders), then none. An invalid address fails that item with invalid_field (param reply_to). |
attachments | object[] | optional | Per-item attachments (up to 20), each inline ({ filename, content, content_type? }) or hosted ({ filename, url, content_type? }). A bad/oversize attachment fails that item with invalid_field (param attachments) or message_too_large — the rest still send. See Send → Attachments. |
Example request body:
Reply-To. Each item may carry reply_to — a single email or an array of emails. Precedence per item is request reply_to → the resolved sender's default reply_to → none; set a per-sender default under Settings → Senders. An invalid address fails that item with invalid_field (param: "reply_to"), leaving the rest of the batch unaffected.
When the batch is accepted the API returns 207 Multi-Status with a data array (one entry per item, in request order) and a summary. Each entry is either a success or a failure:
| Field | Description |
|---|---|
index | The item's zero-based position in the request emails array. |
status | "sent", "failed", or "duplicate". |
id | On "sent": the email's UUID, usable with GET /api/v1/emails/{id}. |
error | On "failed": an object with code, message, and an optional param. |
duplicate_of | On "duplicate": the index of the earlier byte-identical entry this one collapsed into. |
summary | { total, sent, failed, duplicates } counts across the batch. |
Example 207 body:
Items are independent. A malformed item (missing to/subject/html), or one the provider rejects, fails on its own and does not roll back the others. Every successful item is metered, sent, and logged independently, producing its own delivery and scan rows just like a single send. The failed count is refunded so your meter reflects only real sends.
Some conditions stop the entire batch before any item is sent. These return a single error envelope (not a 207):
| Status | code | When |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
403 | transactional_paused | Transactional sending is paused for review on this account. |
402 | overage_cap_reached | The workspace spend cap was reached — the batch meters fail-closed. |
429 | rate_limited | The per-key rate limit (1000/minute) was exceeded. Sets Retry-After. |
503 | service_unavailable | Metering is briefly unavailable — the batch fails closed (no sends). Retryable. |
503 | suppression_unverifiable | The suppression list couldn't be verified for the batch — it fails closed (no sends). Retryable. |
Example whole-batch error:
See Error codes for the canonical envelope and the complete list.