Batch sending
Send up to 100 emails in one request with all-or-nothing acceptance.
POST /v1/emails/batch accepts up to 100 messages in a single request. The batch is atomic: either every message is accepted and you get every id, or nothing is persisted and you get one error.
Send a batch
The request body is a top-level JSON array of 1–100 email objects. Each object takes exactly the same fields as a single POST /v1/emails: templates, tags, and scheduled_at all work per item.
curl -X POST https://api.mailfully.com/v1/emails/batch \
-H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '[
{
"from": "orders@mail.acme.com",
"to": "you@yourcompany.com",
"subject": "Your order shipped",
"html": "<p>Your order is on its way.</p>",
"tags": [{ "name": "category", "value": "order-shipped" }]
},
{
"from": "orders@mail.acme.com",
"to": "you@yourcompany.com",
"subject": "Your order shipped",
"text": "Order 4212 shipped.",
"scheduled_at": "2026-07-10T09:00:00Z"
}
]'Replace you@yourcompany.com with an address you own before running this.
Success is a 202 with one id per input object, in request order:
{
"data": [
{ "id": "01JZFYK2N8Q4T7W9E1R3M5X0AB" },
{ "id": "01JZFYK2NAB3C5D7E9F1G3H5J7" }
]
}
Correlate ids by position: data[0] is the first object you sent, data[1] the second.
All-or-nothing acceptance
There are no per-item results. Every object is validated and prepared before anything is written, and the first invalid item aborts the entire batch with a single 422 validation_error whose param prefixes the offending item's index ([i] or [i].<field>, for example [37].to). The API persists and enqueues none of it.
A body that isn't a JSON array fails with 422 validation_error: Batch body must be a JSON array of email objects. An array outside 1–100 items fails the same way.
One caveat to the [i] prefix: an unknown template.id in any item fails the batch with a plain 404 not_found (Template not found.) that does not name the offending index. All-or-nothing still holds. Nothing is persisted. If a batch 404s, check each item's template.id. This is current behavior.
Gates run once per batch
The org-level accept gates evaluate the batch as a unit, in this order:
- Verified domain: a live batch with no verified sending domain is rejected with
403 unverified_domain_required. - Spend cap, then monthly ceiling: the monthly check projects
current + Nfor a batch of N; crossing either limit returns429 spend_cap_exceededor429 monthly_quota_exceeded(live only). - Daily cap: the batch consumes one unit per message; a denial part-way through returns
429 daily_quota_exceededwith aRetry-Afterheader, and the units checked before the denial stay consumed.
One denial rejects the whole batch. A batch is also a single request, so it makes one pass through rate limiting and idempotency rather than N.
Idempotency
The Idempotency-Key header covers the entire batch — one key for the whole array, with no per-item keys:
const { data, error } = await mailfully.emails.batch(inputs, {
idempotencyKey: "import-2026-07-08",
});
Semantics match the single send:
- Same key + same body within 24 hours replays the original
202verbatim, including the same ids. - Same key + a different array is rejected with
409 invalid_idempotent_request. - Two concurrent requests with the same key: one wins, the other gets
409 concurrent_idempotent_requests. - Only 2xx responses are cached. A rejected batch releases the key, so you can fix the offending item and retry with the same key.
See Idempotency for the full contract.
Batch or loop?
Use the batch endpoint when:
- The messages are generated together (an import, a digest run, a fan-out of the same notification) and partial acceptance would leave you reconciling which half went out.
- You want atomic accept semantics: all ids or one error.
- You want fewer requests through rate limiting and the accept gates.
Loop single sends instead when:
- You need per-message error isolation. In a batch, one invalid address aborts all 100; looped sends fail independently.
- You need per-message idempotency keys, for example one key per order id. A batch key covers the whole array.
- Messages are created at different moments rather than as one unit of work.
Caps to keep in mind: 100 items per batch, and each item allows up to 50 combined to, cc, and bcc recipients.
Related
POST /v1/emails/batch: full request and response reference- Idempotency
- Rate limits