import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const result = await client.messages.batch([
{
agent: "hello",
to: "jordan@example.com",
subject: "Your receipt for order #10473",
body_text: "Thanks for your purchase.",
},
{
agent: "hello",
to: "alex@example.com",
subject: "Your receipt for order #10474",
body_text: "Thanks for your purchase.",
},
]);
console.log(result);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
result = client.batch_send([
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase.",
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase.",
},
])
print(result)curl -X POST 'https://api.mails.ai/v1/messages/batch' \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H 'Content-Type: application/json' \
-d '[
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase."
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase."
}
]'mails emails batch --body-file ./batch.json
cat batch.json | mails emails batch --data -{
"name": "mails_emails_send_batch",
"arguments": {
"messages": [
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase."
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase."
}
]
}
}{
"data": [
{
"id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"agent_id": "agt_01JZ9F8H7KQ2M3N4P5R6S7T8U9",
"thread_id": "thrd_01JZX8K3M9Q4P7VN2YB6RTDC11",
"to": ["jordan@example.com"],
"subject": "Your receipt for order #10473",
"classifier_score": 0.01,
"status": "sent",
"cost_usd": 0.0004,
"test_mode": false,
"created_at": "2026-06-24T17:32:08.421Z"
}
],
"batch_size": 2
}201 Per-message results (success or error objects) plus
the submitted batch size.
400 Invalid request — malformed JSON or a field failed
validation. Also feature_not_enabled when this
server has a feature switched off (custom domains
before they are available).
401 Missing or invalid API key.
403 The API key lacks the required scope
(insufficient_scope), or the token belongs to an
app connected by sign-in and this operation is
closed to connected apps
(connected_app_not_allowed).
409 Conflict — duplicate resource, an in-progress
idempotent request, a draft or scheduled message
that started sending while it was being changed, or
a thread deleted for good while one of its messages
is being sent (duplicate_resource); or an agent
name more than one agent has (agent_name_conflict:
use the agent's id); or a status change on an
archived agent, since archiving cannot be undone
(agent_archived).
422 Unprocessable — agent paused, recipient suppressed,
a recipient at test.mails.ai other than
reply@test.mails.ai (unknown_test_address), or
abuse guard.
429 Rate or quota limit exceeded. The body's resets_at
and retry_after_seconds say when the request can
succeed. A Retry-After header is sent only when
that is 60 seconds or less; a longer wait (an
hourly, daily or monthly cap) sends none, so retry
at resets_at or raise the limit instead of
sleeping.
500 Internal server error.Send a batch of messages
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const result = await client.messages.batch([
{
agent: "hello",
to: "jordan@example.com",
subject: "Your receipt for order #10473",
body_text: "Thanks for your purchase.",
},
{
agent: "hello",
to: "alex@example.com",
subject: "Your receipt for order #10474",
body_text: "Thanks for your purchase.",
},
]);
console.log(result);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
result = client.batch_send([
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase.",
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase.",
},
])
print(result)curl -X POST 'https://api.mails.ai/v1/messages/batch' \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H 'Content-Type: application/json' \
-d '[
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase."
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase."
}
]'mails emails batch --body-file ./batch.json
cat batch.json | mails emails batch --data -{
"name": "mails_emails_send_batch",
"arguments": {
"messages": [
{
"agent": "hello",
"to": "jordan@example.com",
"subject": "Your receipt for order #10473",
"body_text": "Thanks for your purchase."
},
{
"agent": "hello",
"to": "alex@example.com",
"subject": "Your receipt for order #10474",
"body_text": "Thanks for your purchase."
}
]
}
}{
"data": [
{
"id": "msg_01JZX8K3M9Q4P7VN2YB6RTDC0E",
"agent_id": "agt_01JZ9F8H7KQ2M3N4P5R6S7T8U9",
"thread_id": "thrd_01JZX8K3M9Q4P7VN2YB6RTDC11",
"to": ["jordan@example.com"],
"subject": "Your receipt for order #10473",
"classifier_score": 0.01,
"status": "sent",
"cost_usd": 0.0004,
"test_mode": false,
"created_at": "2026-06-24T17:32:08.421Z"
}
],
"batch_size": 2
}201 Per-message results (success or error objects) plus
the submitted batch size.
400 Invalid request — malformed JSON or a field failed
validation. Also feature_not_enabled when this
server has a feature switched off (custom domains
before they are available).
401 Missing or invalid API key.
403 The API key lacks the required scope
(insufficient_scope), or the token belongs to an
app connected by sign-in and this operation is
closed to connected apps
(connected_app_not_allowed).
409 Conflict — duplicate resource, an in-progress
idempotent request, a draft or scheduled message
that started sending while it was being changed, or
a thread deleted for good while one of its messages
is being sent (duplicate_resource); or an agent
name more than one agent has (agent_name_conflict:
use the agent's id); or a status change on an
archived agent, since archiving cannot be undone
(agent_archived).
422 Unprocessable — agent paused, recipient suppressed,
a recipient at test.mails.ai other than
reply@test.mails.ai (unknown_test_address), or
abuse guard.
429 Rate or quota limit exceeded. The body's resets_at
and retry_after_seconds say when the request can
succeed. A Retry-After header is sent only when
that is 60 seconds or less; a longer wait (an
hourly, daily or monthly cap) sends none, so retry
at resets_at or raise the limit instead of
sleeping.
500 Internal server error.Headers
Replays the stored response for a repeated key (kept 24 hours), so a retried request never double-sends; the replay carries Idempotent-Replay: true. An error is kept on the key only once the mail has gone out, so a replayed error means it was sent. Once a scheduled send is stored, a failure after that keeps the message's answer on the key instead: the retry answers with the message (201) and raises its message.scheduled if the first request did not. After a failure before either, the same key sends again, as it does after a batch that stored no message and sent no mail.
Per-message failures are returned inline in data[] and do not fail the batch; the HTTP status is still 201. Each failure is the error a single send would answer: a message a send limit refused carries resets_at and retry_after_seconds, one the email service would not take for now (503 upstream_unavailable) retry_after_seconds, and a plan refusal upgrade_url. With an Idempotency-Key, a batch in which no message was stored and no mail went out frees the key, so sending it again with the same key sends it; any other batch's answer is kept on the key. A scheduled message is its result once stored, even if announcing it then fails: with a key the batch answers that error, and the retry with the same key gets the batch (201) and raises the message.scheduled. A batch cannot send attachments: an item carrying attachments refuses the whole batch with 422 invalid_field (param attachments), so send that message on its own with POST /v1/messages.
Response
One result per submitted message, in request order: the accepted message, or an error object for one that was refused. A refused message does not stop the others.
How many messages the request held; data has one entry for each.
Was this page helpful?