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

# Send a batch of emails

`POST /email/batch`

## Request

- Base URL: `https://www.unitpost.com/api/v1`
- URL: `https://www.unitpost.com/api/v1/email/batch`
- Auth: HTTP bearer

Send up to 100 transactional emails in a single request. Validation is all-or-nothing: if any item is invalid the whole batch is rejected and nothing is queued, so you never end up with a partial send. Pass an `Idempotency-Key` header to make retries safe — the same key replays the original result instead of re-sending. The response returns a `batch_id`; track the batch's per-status rollup with [Retrieve a batch](#get-email-batches-id) and stop the rest of it with [Cancel a batch](#post-email-batches-id-cancel). To send one email, use [Send or schedule an email](#post-email). Requires the `emails:send` capability.

## Request body

- union
  - object[]
    - `from` string, required: Sender address on one of your verified domains. Accepts a bare address ("hi@acme.com") or a display name ("Acme <hi@acme.com>") — the name is what inbox clients show next to the message.
    - `to` union, required
      - string
      - string[]
    - `subject` string
    - `cc` union
      - string
      - string[]
    - `bcc` union
      - string
      - string[]
    - `reply_to` union
      - string
      - string[]
    - `in_reply_to` string
    - `headers` object: Custom headers (your own `X-*` and threading headers). Envelope (`From`/`To`/`Cc`/`Bcc`/`Reply-To`/`Subject`/`Date`/`Message-ID`) and signing/transport headers are managed for you and rejected with 422. On a non-marketing send you may set your own `List-Unsubscribe` (one or more `<…>` entries, each `https://` or `mailto:`) and `List-Unsubscribe-Post` (exactly `List-Unsubscribe=One-Click`, which needs at least one `https://` entry). A marketing-template send refuses both: Unitpost adds its own per recipient.
    - `tags` object[]
      - `name` string, required
      - `value` string, required
    - `template` object: Published template plus variable values. Mutually exclusive with `html` and `text`.
      - `id` string, required
      - `variables` object
    - `html` string: HTML MIME body. It is sanitized for email safety and may be supplied with `text` to create a multipart alternative. This field does not parse Markdown.
    - `text` string: Plain-text MIME body. Newline characters are preserved; Markdown and rich-text styling are not interpreted. It may be supplied with `html` as the fallback alternative. When `html` is sent without `text`, a plain-text alternative is derived from the visible HTML content; an explicit `text` value is always sent unchanged.
    - `open_tracking` boolean
    - `click_tracking` boolean
  - object
    - `emails` object[], required
      - `from` string, required: Sender address on one of your verified domains. Accepts a bare address ("hi@acme.com") or a display name ("Acme <hi@acme.com>") — the name is what inbox clients show next to the message.
      - `to` union, required
        - string
        - string[]
      - `subject` string
      - `cc` union
        - string
        - string[]
      - `bcc` union
        - string
        - string[]
      - `reply_to` union
        - string
        - string[]
      - `in_reply_to` string
      - `headers` object: Custom headers (your own `X-*` and threading headers). Envelope (`From`/`To`/`Cc`/`Bcc`/`Reply-To`/`Subject`/`Date`/`Message-ID`) and signing/transport headers are managed for you and rejected with 422. On a non-marketing send you may set your own `List-Unsubscribe` (one or more `<…>` entries, each `https://` or `mailto:`) and `List-Unsubscribe-Post` (exactly `List-Unsubscribe=One-Click`, which needs at least one `https://` entry). A marketing-template send refuses both: Unitpost adds its own per recipient.
      - `tags` object[]
        - `name` string, required
        - `value` string, required
      - `template` object: Published template plus variable values. Mutually exclusive with `html` and `text`.
        - `id` string, required
        - `variables` object
      - `html` string: HTML MIME body. It is sanitized for email safety and may be supplied with `text` to create a multipart alternative. This field does not parse Markdown.
      - `text` string: Plain-text MIME body. Newline characters are preserved; Markdown and rich-text styling are not interpreted. It may be supplied with `html` as the fallback alternative. When `html` is sent without `text`, a plain-text alternative is derived from the visible HTML content; an explicit `text` value is always sent unchanged.
      - `open_tracking` boolean
      - `click_tracking` boolean
    - `scheduled_at` string, date-time

## Response `200`

Batch accepted.

- BatchSendResult
  - `object` 'list', required
  - `batch_id` string, required: Handle for GET / cancel on /email/batches/{id}.
  - `status` 'queued' | 'scheduled', required
  - `scheduled_at` string, nullable, required
  - `data` object[], required
    - `id` string, required
  - `failed` object[], required
    - `index` integer, required
    - `error` string, required

## Other responses

- `401`: Missing or invalid API key.
- `403`: Insufficient scope, or sending domain not found / not verified, or (marketing template) adding the recipients as contacts would pass the Free plan's contact limit (`limit_reached`).
- `409`: Idempotency conflict (same key, different body / in-flight).
- `422`: Validation error (the whole batch was rejected), or (marketing template) a recipient is an older or secondary address of another contact.
- `429`: Too many requests, or sending is paused: `rate_limit_exceeded` (request rate), `quota_exceeded` (free-tier daily/monthly cap, or the plan's monthly sending allowance and any purchased credit are spent), or `overage_cap_exceeded` (a paid plan reached its overage cap — sending resumes when the allowance refreshes, or buy a credit top-up). The whole batch is gated at the door — nothing is queued. Includes a `Retry-After` header with the seconds until the window resets (the billing period end for quota/overage blocks).

---

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