---
title: "Send a batch of emails"
method: POST
path: "/transactional/emails/batch"
tags: ["Transactional"]
---

# Send a batch of emails

`POST /transactional/emails/batch`

## Request

- Base URL: `https://api.sendfox.com`
- URL: `https://api.sendfox.com/transactional/emails/batch`
- Auth: OAuth 2
  - Authorization code: `https://sendfox.com/oauth/authorize`
  - Token URL: `https://sendfox.com/oauth/token`

Queues up to 100 emails in one request and reports a result per item.
Items are independent: one invalid address, unverified sender or
suppressed recipient is reported at its `index` and the rest are
queued. The response is `200` whenever the batch itself was accepted,
even if every item was refused; read each entry's `error`.

**Limits.** The whole batch's sends (one per `to`, `cc` and `bcc`
address across every item) are reserved against your plan before
any item is looked at. A batch that does not fit is refused with
`429 monthly_quota_exceeded` and nothing is sent, so a retry can
never half-send. Sends for refused items, test addresses and replays
are returned. A batch request counts as one request against the 5
per second and 300 per minute rate limit; the plan's sends are what
bound volume.

**Idempotency.** Two levels. An `Idempotency-Key` header makes the
whole batch replayable for 24 hours: the same key with the same
`emails` returns the original results with `idempotent-replayed: true`;
a different body is a `409 invalid_idempotent_request`. A per-item
`idempotency_key` protects each email on its own, exactly like the
header on `POST /transactional/emails`, and is the one to rely on when
your retry may be built from a different batch.

Sandbox, test addresses and tags work per item as on the single route.

## Headers

- `Idempotency-Key` string

## Request body

- TransactionalBatchSendRequest
  - `emails` object[], required: 1 to 100 emails. Each item takes the same fields as `POST /transactional/emails` plus an optional `idempotency_key`. The `html` and `text` of all items together may total at most 5 MB.
    - `from` string, required: Sender. Either an address or a display name followed by one in angle brackets. The domain must be plain ASCII (for an internationalized domain, use its punycode form, starting xn--) and verified under Account > Domains (a subdomain of a verified domain also works), or be the sandbox sender `onboarding@sendfoxmail.com`, which needs no DNS but only delivers to your own account email and the test addresses (sandbox mail always shows the SendFox Onboarding display name).
    - `to` string[], required: 1 to 10 addresses. to, cc and bcc together may hold at most 10, and every address counts as one email against your plan's sends, so a message to three people uses three.
    - `cc` string[]
    - `bcc` string[]: At most 2 addresses; counts toward the 10-address total.
    - `reply_to` string, email, nullable
    - `subject` string, required
    - `html` string: HTML body, at most 500 KB (UTF-8 bytes). Send html, text, or both.
    - `text` string: Plain-text body, at most 100 KB (UTF-8 bytes). Send html, text, or both.
    - `tag` string, nullable: A label for grouping in the dashboard, for per-tag open/click tracking switches, and for per-tag alerts. Lowercase letters, digits, dots, dashes and underscores. Messages without a tag are grouped by subject. Tags that look like authentication mail (`login_code`, `password_reset`, `otp`, ...) can never have tracking turned on. An account may hold at most 200 distinct tags, so tag by kind of email (`receipt`), not per customer or order.
    - `headers` object, nullable: Custom headers, at most 20, as `{"Name": "value"}`. Allowed: `List-Unsubscribe`, `List-Unsubscribe-Post`, `List-Id`, `X-Entity-Ref-ID`, `References`, `In-Reply-To`, and any other `X-*` header except `X-SendFox-*` and `X-SG-*`. Headers SendFox or SendGrid set (`From`, `To`, `Cc`, `Bcc`, `Reply-To`, `Subject`, `Content-*`, `MIME-Version`, `Date`, `Message-ID`, `Return-Path`, `DKIM-Signature`, `Received`) are refused with a 422. Names may hold letters, digits and dashes; values may hold up to 998 characters and no line breaks. `List-Unsubscribe` must be one or more `mailto:` or `https:` URLs in angle brackets, and `List-Unsubscribe-Post` must be exactly `List-Unsubscribe=One-Click` with an `https:` URL in `List-Unsubscribe`, as RFC 8058 requires.
    - `attachments` object[], nullable: Files to attach, at most 10 and 5 MB in total once decoded. Each is `{filename, content, content_type?}` with `content` the file in base64 (line breaks are fine). `content_type` is guessed from the extension when omitted and may carry parameters, like `text/calendar; method=REQUEST` for a calendar invite. Executable and script files (`.exe`, `.js`, `.bat`, `.scr`, ...) are refused, as are URLs (`path`): send the file itself. Not accepted on the batch endpoint. The file is kept only until the email is sent or fails; `GET /transactional/emails/{id}` shows its name, type and size.
      - `filename` string, required
      - `content` string, byte, required: The file, base64-encoded.
      - `content_type` string
    - `idempotency_key` string: Optional, unique per logical email. Works like the `Idempotency-Key` header on the single route: within 24 hours the same key with the same item returns the original message (`replayed: true`); a different item is a per-item `invalid_idempotent_request` error.

## Response `200`

Batch accepted; one result per item

- object
  - `data` TransactionalBatchResult[]
    - `index` integer, required
    - `id` string
    - `status` string
    - `replayed` boolean: Present (`true`) when the item's `idempotency_key` matched an earlier email.
    - `error` object
      - `status` integer
      - `name` string
      - `message` string
      - `fix` string
      - `link` string
      - `errors` object: Field errors, on `validation_error` only.

## Other responses

- `401`: missing_api_key or invalid_api_key
- `403`: insufficient_scope, feature_not_available, account_restricted
- `409`: invalid_idempotent_request (the Idempotency-Key header was used with a different body)
- `422`: validation_error on the envelope (`emails` missing, not a list, empty, over 100 items or over 5 MB)
- `429`: rate_limit_exceeded, or monthly_quota_exceeded when the whole batch does not fit the plan's remaining sends

## Changes

- **2026-10-15** `f62c3f4840f1`: 2 warning
  - the optional response header `x-sendfox-daily-quota-remaining` removed for the status `200`
  - the optional response header `x-sendfox-monthly-quota-remaining` removed for the status `200`
- **2026-10-14** `82cbee9ea496`: 1 info
  - added the new optional request property `emails/items/allOf[#/components/schemas/TransactionalSendRequest]/attachments`
- **2026-10-13** `066d2e37ee10`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/sendfox/apis/sendfox-api/changes/transactional/emails/batch/post.md)

---

[API](https://skmtc.dev/sendfox/apis/sendfox-api.md) - [All operations](https://skmtc.dev/sendfox/apis/sendfox-api/llms.txt) - [OpenAPI document](https://skmtc.dev/sendfox/apis/sendfox-api/revisions/e45b66045d53?raw)
