Send a batch
/v1/send/batchSecret key — sk_… or sk_test_… · Base URL https://api.volanea.com
Up to 1,000 personalized messages in one call. Each message succeeds or fails independently; a failed message becomes a { error: … } entry in results rather than failing the whole batch, so a bad template id in message #7 does not stop #8.
The batch answers 200 even when every message failed. results is positional — entry i is the outcome of messages[i] — and you must inspect each entry. A caller that only checks the HTTP status reports a completely failed batch as a success. Supports Idempotency-Key over the whole batch.
Body
Required · application/json
messagesobject[]requiredtoemail | object | email | object[]requiredRecipient address (optionally with a display name), or an array of 1–50 recipients (each gets its own message).
subjectstringSubject line. Supports `{{variable}}` placeholders, with fallbacks: `{{firstName | "there"}}`.
htmlstringHTML body. Supports `{{variable}}` placeholders, with fallbacks: `{{firstName | "there"}}`.
textstringPlain-text body (wrapped in `<pre>` when no HTML is given).
templateIdstringSend using a stored template instead of inline content.
variablesobjectValues for `{{variable}}` placeholders in the subject and body. A name you omit falls back to the same-named contact field, then to the placeholder's own `| "default"` if it has one, then to nothing.
fromemailFrom address. Must belong to a verified domain (unless in test mode). Defaults to the template's from address, or hello@ your first verified domain.
fromNamestringreplyToemailSets a Reply-To header.
headersobjectCustom email headers to include on the message.
attachmentsobject[]Up to 10 attachments per message. Total request size is capped at 10 MiB.
filenamestringrequiredcontentstringrequiredBase64-encoded file content.
contentTypestring
sendAtdate-timeSchedule delivery for a future time (ISO 8601 with offset). The message is stored `queued` with a `scheduledFor` — list the pending ones with `GET /v1/emails?status=scheduled`, stop one with `DELETE /v1/emails/{id}`.
createContactbooleanSet `false` to skip auto-creating a contact for unknown recipients (default `true`).
typeenumMarketing messages get the unsubscribe footer and skip contacts who have unsubscribed. Defaults to the template's type, or `transactional` for inline content — so promotional copy sent as inline `html` must set this explicitly or it bypasses the unsubscribe check.
transactionalmarketing
Response
Per-message results, in request order.
resultsobject | object[]requiredtestModebooleanrequiredreplayenumPresent and `true` ONLY on an idempotent replay — absent on a first send, never `false`. Nothing was dispatched; `results` is the stored response from the original batch.
true
Errors
Every failure returns the standard envelope — branch on `code`, never on the wording of `error`.
401Missing or invalid API key.409`Idempotency-Key` reused with a different body (`idempotency_key_reused`) — nothing was sent.422Request validation failed (code `validation_error`, 422).429Per-project send rate limit (`rate_limited`). A batch consumes one unit per message, so it cannot dodge the cap.
Request
curl https://api.volanea.com/v1/send/batch \
-X POST \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "ada@example.com",
"subject": "Your order shipped",
"html": "<p>On its way, {{firstName}}.</p>",
"text": "On its way, Ada.",
"templateId": "tpl_9f2c",
"variables": {},
"from": "hello@yourdomain.com",
"fromName": "Acme",
"replyTo": "support@yourdomain.com",
"headers": {},
"attachments": [
{
"filename": null,
"content": null,
"contentType": null
}
],
"sendAt": "2026-01-01T09:00:00Z",
"createContact": true,
"type": "transactional"
}
]
}'Response
200 · application/json
{
"success": true,
"data": {
"results": [
{
"messages": []
}
],
"testMode": true,
"replay": true
}
}92 endpoints · OpenAPI document