Batch sending

Up to 100 emails in one request, each with its own recipient, body, and result.

POST /v1/emails/batch takes an array of the exact bodies POST /v1/emails takes, up to 100 of them. Each entry is its own email to its own recipient: nobody sees anybody else's address, and one bad entry does not stop the rest.

POST /v1/emails/batch

Send up to 100 in one request.

Two emails, one request
curl -X POST https://emails.sh/v1/emails/batch \
  -H "Authorization: Bearer $EMAILSSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      {
        "from": "Acme <hello@acme.com>",
        "to": ["ada@example.com"],
        "subject": "Your invite to Acme",
        "html": "<p>Hello Ada, here is your invite.</p>"
      },
      {
        "from": "Acme <hello@acme.com>",
        "to": ["grace@example.com"],
        "subject": "Your invite to Acme",
        "html": "<p>Hello Grace, here is your invite.</p>"
      }
    ]
  }'
207 Multi-Status
{
  "data": [
    { "id": "em_01J9X8Q2K7Y4RN3M", "status": "queued" },
    { "id": null, "status": "failed", "error": { "code": "recipient_suppressed", "message": "grace@example.com hard bounced on 2026-07-02 and is suppressed on this workspace", "next": "Remove it at https://emails.sh/dashboard/suppressions if you know it is good." } }
  ]
}

The array comes back in the order you sent it, one entry per input, so index 3 in the response is index 3 in the request. A refused entry has id: null and an error object saying why. Walk it and record the ids; an entry with an error never sent and will not retry itself.

emails.batch

Send up to 100 emails in one request. Each entry succeeds or fails on its own, and the response keeps the order you sent them in.

Arguments

emails Send[] required
Up to 100 send bodies, each exactly as emails.send takes one.

Returns{ data: ({ id, status } | { id: null, status: "failed", error })[] }

What batch is not

It is not a mailing list, and it does not template. Every entry carries its own rendered subject and body, so personalisation happens in your code before the call. If you want a list with subscription state, see /docs/audiences.

The whole request counts as its entries against your quota, so 100 emails in one batch and 100 sent one at a time draw down the same allowance. What batch saves is round trips, which matters when you are sending from a serverless function with a wall-clock budget.