Get batch status
Poll the status of a batch send and, once finished, list the message ids it produced.
Returns the processing state of a batch created with POST /v3/messages.batch. Every batch is accepted asynchronously: it starts as accepted the moment the 201 (with its unchanged body and every message_id) is returned, moves to processing while the background worker persists it in chunks, and ends as completed, partial (some recipients could not be persisted) or failed. Most batches finish within seconds. It is a cheap read — poll it every few seconds.
GET /v3/batches/{batch_id}
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
batch_id | string | Required | The batch_id returned by POST /v3/messages.batch — always starts with batch_. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | Optional | Page size for messages (default 1000, max 10000). Only applies once the batch is finished. |
cursor | string | Optional | Keyset cursor — pass the previous page’s next_cursor to fetch the next page of messages. |
Response
Returns { batch_id, account_id, status, recipients, accepted, rejected, failed, created_at, started_at, finished_at, error?, failed_recipients?, messages?, next_cursor? }. recipients is the number of recipient entries you submitted; accepted counts the entries persisted as queued messages so far; rejected the entries refused at validation time (invalid address, suppressed, quota overflow under partial-fill); failed the accepted entries whose persist failed permanently — their quota is released and the first 100 are listed in failed_recipients with an error code.
accepted— validated, quota reserved, waiting for the worker.processing— the worker is persisting the batch;acceptedgrows chunk by chunk.completed— every recipient entry is persisted;messageslists them all.partial— finished, but some entries failed to persist (seefailed/failed_recipients).failed— finished and nothing could be persisted (errorcarries the last error code).
Once the batch is finished the response also carries messages: one { message_id, recipient, status } per persisted row (queued and rejected rows alike, so the list is the audit of the whole batch), ordered by message id and paginated with next_cursor (null on the last page). Look a single message up with GET /v3/messages/{id}.
{
"batch_id": "batch_7f3a2b1c9d8e4f50",
"account_id": "acc_3f9a1c2b7d8e4f01",
"status": "completed",
"recipients": 840,
"accepted": 838,
"rejected": 2,
"failed": 0,
"created_at": "2026-09-10T07:00:00.000Z",
"started_at": "2026-09-10T07:00:01.000Z",
"finished_at": "2026-09-10T07:00:06.000Z",
"messages": [
{ "message_id": "msg_a1b2c3d4e5f64789", "recipient": "alex@example.com", "status": "queued" },
{ "message_id": "msg_b2c3d4e5f6a74890", "recipient": "ben@example.com", "status": "rejected" }
],
"next_cursor": null
}Responses
| Status | Description |
|---|---|
200 | OK. The batch status object; messages + next_cursor are present once status is completed, partial or failed. |
400 | batch_id does not start with batch_, cursor is not a message id, or limit is out of range. |
401 | Missing or invalid API key. |
404 | The batch doesn't exist, belongs to another account, or its status expired (asynchronous batch status is kept for 48 hours; the messages themselves stay in your Activity log). |
Code examples
curl https://api.wemail.io/v3/batches/{batch_id} \
-H "Authorization: Bearer afn_live_…"const res = await fetch("https://api.wemail.io/v3/batches/{batch_id}", {
headers: {
Authorization: `Bearer ${process.env.WEMAIL_API_KEY}`,
},
});
const data = await res.json();import os, requests
r = requests.get(
"https://api.wemail.io/v3/batches/{batch_id}",
headers={"Authorization": f"Bearer {os.environ['WEMAIL_API_KEY']}"},
)
data = r.json()