Batch sending
POST /v1/emails/batch takes up to 100 messages in one request. Each is an ordinary
EmailCreate object, so anything a single send accepts works
here.
{
"emails": [
]
}
Choose how strict to be
This is the decision worth making deliberately, because the two modes fail in opposite ways.
strict, the default. Every message is validated before anything is sent. If one is
invalid the whole batch is rejected with 422, each failure named by index, and nothing is
sent.
{
"type": "validation_error",
"message": "One or more messages in the batch are invalid. Nothing was sent.",
"details": [
{"field": "emails[1]", "message": "Domain not found", "code": "domain_not_found"}
]
}
Use this when the batch is one logical unit, or when a partial send would be worse than none.
permissive. Valid messages are sent, invalid ones are reported.
{"validation": "permissive", "emails": [...]}
{
"batch_id": "...",
"results": [
{"index": 0, "id": "...", "status": "pending"},
{"index": 1, "status": "failed"}
],
"errors": [
{"index": 1, "code": "domain_not_found", "message": "Domain not found"}
]
}
Use this when the messages are independent and you would rather deliver 99 than none.
Read errors either way. Permissive returns 200 even when items failed, so treating a
200 as complete success is the mistake to avoid.
Indices are stable
index always refers to the position in the array you sent, so you can map a result back onto
your own records without tracking which items were skipped.
Quota and rate limits
Every recipient counts against your send allowance, whether it arrived in a batch or on its own. Batching saves request allowance rather than send allowance: one batch of 100 costs one request against your rate limit instead of a hundred.
That is the main reason to batch. If you are sending a hundred messages in a loop and hitting
rate_limit_exceeded, this is the fix.
Idempotency
An Idempotency-Key covers the whole batch. Retrying with the same key returns the original
result rather than sending any message twice. Do not vary the key per retry, and do not send
the same key with a different array. See Idempotency.
When not to batch
Batching is not a scheduler. If the messages should go out at different times, use
scheduled_at on individual sends rather than holding a batch until the right moment.