---
title: "Create a batch of email messages"
method: POST
path: "/email_messages/batch"
tags: ["Email Messages"]
---

# Create a batch of email messages

`POST /email_messages/batch`

Creates up to 1,000 email messages in a single request. Request-wide admission checks run first and can reject the whole batch before message creation. After those checks pass, each message is validated and sent independently; item-level failures do not affect other messages, and the processed batch returns 207 Multi-Status. Per-message failures include validation errors; when a template has `strict_variables` enabled, a missing required variable produces a per-item `unprocessable_entity` error naming that variable while the other messages continue.

## Headers

- `Idempotency-Key` string

## Request body

- CreateEmailBatchRequest
  - `messages` CreateEmailBatchItemRequest[], required — Array of email messages to send. Up to 1,000 messages per batch request. Each message is validated and sent independently; per-message failures do not affect other messages in the batch.
    - `from` union, required
      - string
      - EmailAddress
        - `email` string, required
        - `name` string
    - `from_name` string — Optional display name for string `from`; overrides `from.name` when provided.
    - `to` EmailAddressInput[], required
      - union
        - string
        - EmailAddress
          - `email` string, required
          - `name` string
    - `cc` EmailAddressInput[]
      - union
        - string
        - EmailAddress
          - `email` string, required
          - `name` string
    - `bcc` EmailAddressInput[]
      - union
        - string
        - EmailAddress
          - `email` string, required
          - `name` string
    - `reply_to` union
      - string
      - EmailAddress
        - `email` string, required
        - `name` string
    - `subject` string — Required unless `template_id` is supplied. When using a template, the template's subject is rendered; if the template has no subject or renders empty, the request returns 400.
    - `html_body` string — HTML email body. Returned only by `GET /email_messages/{id}`; omitted from create and list responses.
    - `text_body` string — Plain text email body. Returned only by `GET /email_messages/{id}`; omitted from create and list responses.
    - `headers` object — Custom email headers. Write-only; not returned in responses.
    - `attachments` AttachmentRequest[]
      - `filename` string — Attachment filename. Defaults to "attachment" when omitted.
      - `content_type` string — MIME content type. Defaults to "application/octet-stream" when omitted.
      - `content` string — Attachment content, typically Base64-encoded. Defaults to empty string when omitted.
      - `disposition` string — MIME disposition (`attachment` or `inline`).
      - `content_id` string, nullable — MIME Content-ID used to reference an inline attachment.
    - `tags` string[] — Tags for categorization and filtering. Stored on the message, returned on message responses, and propagated to Email Detail Records. Usable in `filter[tags]` when listing messages.
    - `group_id` string, uuid, nullable — Optional unsubscribe-group UUID used for group-scoped suppression checks and unsubscribe handling.
    - `ignore_suppression` boolean — When true, allows delivery to recipients whose suppressions explicitly permit an override. Hard bounces, spam complaints, and invalid-address suppressions cannot be overridden. Requires the `email:override` API scope.
    - `metadata` object — Custom metadata key/value pairs. Stored on the message, returned on message responses, and propagated to Email Detail Records. Usable in `filter[metadata]` when listing messages.
    - `tracking_settings` TrackingSettings — Per-send open and click tracking overrides. Omitted properties inherit the sender domain's tracking settings.
      - `open_tracking` boolean — Whether to inject an open-tracking pixel for this message.
      - `click_tracking` boolean — Whether to rewrite links for click tracking in this message.
    - `template_id` string, uuid
    - `template_variables` object — Variables for Liquid template rendering. Non-object values may cause a 422 validation error on message creation, but are silently treated as an empty object for template rendering. When the template enables `strict_variables`, a missing required variable fails the request with 422 (single send) or a per-item `unprocessable_entity` error (batch) naming the variable; no message is persisted for the failed item.
    - `scheduled_at` string, date-time, nullable — Future ISO 8601 delivery time. Invalid or non-future timestamps are rejected. Single sends return HTTP 422; in batch sends the invalid item is reported in the 207 per-item errors while other items continue. `send_at` remains a deprecated request alias. A non-null `scheduled_at` takes precedence over `send_at`; when `scheduled_at` is omitted or null, `send_at` is used.
    - `send_at` string, date-time — Deprecated alias for `scheduled_at`.
    - `inline_css` boolean
    - `sandbox_mode` boolean — Per-message sandbox flag. The batch-level `sandbox_mode` envelope value is authoritative: it overwrites every message's `sandbox_mode` before processing, including the `false` default when the envelope omits the field. A per-item `sandbox_mode: true` inside a non-sandbox batch is therefore a real send. Set the envelope field to run any batch item in sandbox mode.
  - `sandbox_mode` boolean — Applies sandbox mode to all messages in the batch and overrides any per-message `sandbox_mode` value — each message's effective `sandbox_mode` is exactly this envelope value. Reserved recipients at `test.telnyx.com` produce the deterministic event chains documented on CreateEmailRequest.sandbox_mode; no batch item is injected into the MTA or outbound Kafka path. Sandbox batch items are non-billable, consume no daily-send-limit quota, and feed no delivery-reputation signals.

## Response `207`

Multi-Status — returned after request-wide admission checks pass and item processing runs. Each message is validated and sent independently; per-message failures do not affect other messages in the batch. An invalid or non-future `scheduled_at`/`send_at` is reported as a per-item `unprocessable_entity` error while the remaining items continue. When all messages succeed, `errors` is empty and every item is in `data`; otherwise `data` contains successes and `errors` contains failures. Request-wide gates can instead return 4xx or 5xx.

- EmailBatchResponse
  - `data` EmailMessage[], required
    - `record_type` 'email_message', required
    - `id` string, uuid, required
    - `status` 'queued' | 'scheduled' | 'cancelled' | 'sandbox' | 'sending' | 'sent' | 'failed' | 'deferred' | 'delivered' | 'bounced' | 'complained' | 'rejected' | 'opened' | 'clicked' | 'unsubscribed', required — Current status of an email message. Lifecycle statuses (queued, scheduled, etc.) are set on creation. Delivery statuses (delivered, bounced, etc.) are updated by delivery event consumers.
    - `from` EmailAddress, required
      - `email` string, required
      - `name` string
    - `to` EmailAddress[], required
      - `email` string, required
      - `name` string
    - `cc` EmailAddress[], required
      - `email` string, required
      - `name` string
    - `bcc` EmailAddress[], required
      - `email` string, required
      - `name` string
    - `reply_to` string, nullable, required
    - `subject` string, required
    - `template_id` string, uuid, nullable, required
    - `template_variables` object, required
    - `tags` string[], required — Customer-supplied tags stored with the message.
    - `metadata` object, required — Customer-supplied metadata stored with the message.
    - `attachments` AttachmentResponse[], required
      - `url` string, uri, nullable, required — Telnyx-hosted public URL for the attachment content.
      - `sha256` string, nullable, required — SHA-256 hex digest of the attachment content.
      - `size_bytes` integer, nullable, required — Attachment size in bytes.
      - `filename` string, required
      - `content_type` string, required
      - `disposition` string, required — MIME disposition (e.g. `attachment` or `inline`). Runtime passes through the stored value without enforcing an enum.
      - `content_id` string, nullable, required — MIME Content-ID for inline references.
    - `events` EmbeddedMessageEvent[], required
      - `type` 'queued' | 'deferred' | 'scheduled' | 'cancelled' | 'sandbox' | 'sending' | 'sent' | 'failed' | 'delivered' | 'bounced' | 'complained' | 'suppressed' | 'rejected' | 'opened' | 'clicked' | 'unsubscribed' | 'daily_limit_exceeded' | 'scan_deferred' | 'quarantined' | 'quarantine_released' | 'quarantine_release_dispatched' | 'quarantine_rejected' | 'quarantine_expired' | 'gw_reject' | 'injection_timeout' | 'expired', required — Bare stored event names returned by message history. In addition to the normal send and delivery lifecycle, polling can expose suppression, scan, and quarantine lifecycle rows. Sharp canonical names gw_reject, injection_timeout, and expired distinguish gateway rejection, ambiguous injection timeout, and MTA expiration. The failed and bounced names remain valid for system/admin failures and hard bounces respectively. Existing stored rows retain their original names.
      - `occurred_at` string, date-time, required
      - `payload` object
    - `created_at` string, date-time, required
    - `scheduled_at` string, date-time — Present when a scheduled_at value was stored. Persists even after the scheduled send has been processed or cancelled.
    - `inline_css` boolean — Present when true in the immediate create response. Not persisted; absent on subsequent GET requests.
    - `sandbox` boolean — Present when sandbox mode was used.
    - `recipient_statuses` object — Per-status recipient counts for the message. Present only for outbound messages with recipient rows. Keys are recipient statuses, values are counts. Example: `{"delivered": 998, "bounced": 2}`.
    - `suppressed` SuppressedRecipient[] — Recipients excluded from delivery by suppression checks, with reasons. On batch items, present when that item had suppressed recipients; all other recipients of the item still receive the message. For single sends this information appears at the top level of the response instead (see EmailMessageResponse.suppressed).
      - `to` string, email, required — Suppressed recipient email address.
      - `reason` string, required — Suppression reason returned by the recipient suppression service.
      - `scope` string, required — Scope at which the suppression applies.
      - `override_allowed` boolean, required — Whether an authorized send may override this suppression.
  - `errors` EmailBatchItemError[], required
    - `index` integer, required — Zero-based index of the failed message in the request array.
    - `code` 'bad_request' | 'not_found' | 'forbidden' | 'service_unavailable' | 'unprocessable_entity' | 'validation_error' | 'recipient_suppressed' | 'reputation_suspended', required — Batch item errors use `message` (not `detail`) for the human-readable text.
    - `message` string, required
  - `meta` EmailBatchMeta, required
    - `total` integer, required
    - `succeeded` integer, required
    - `failed` integer, required

## Other responses

- `400` — Bad Request / Validation Failed (10015). Invalid, duplicate, empty, malformed, or overlong Idempotency-Key headers are rejected by Edge with HTTP 400 and error code 10015.
- `401` — Not authorized (10006).
- `409` — A request with the same Idempotency-Key is still being processed (10036). Retry later with the same key and request.
- `413` — Request body exceeds the 8,000,000-byte limit for this endpoint.
- `422` — The Idempotency-Key was already used for a different request (10027).
- `429` — Send rejected before message creation because the account daily recipient quota was exhausted (10011), the sender-domain graduation ceiling was exceeded (domain_graduation_limit_exceeded), or domain reputation is poor (reputation_suspended).
- `503` — Service unavailable (10016), including an unavailable upstream dependency or unavailable Edge idempotency protection for a keyed request.

## Changes

> 93 revisions in range; 1 not diffed.

- **2026-09-23** `16bfa298dc71` — 10 warning, 2 info
  - added the new `expired` enum value to the `data/items/events/items/type` response property for the response status `207`
  - added the new `gw_reject` enum value to the `data/items/events/items/type` response property for the response status `207`
  - added the new `injection_timeout` enum value to the `data/items/events/items/type` response property for the response status `207`
  - added the new `quarantine_expired` enum value to the `data/items/events/items/type` response property for the response status `207`
  - …8 more
- **2026-09-22** `9621f6123ebc` — 2 breaking, 13 warning
  - added `#/components/schemas/DailySendLimitError, #/components/schemas/DomainGraduationLimitExceededError, #/components/schemas/ReputationSuspendedError` to the response body `oneOf` list for the response status `429`
  - the response's body type changed from `object` to no type for status `429`
  - removed the optional property `errors` from the response with the `429` status
  - added the new `10011` enum value to the `errors/items/code` response property for the response status `400`
  - …11 more
- **2026-09-16** `a6a61a29ecdd` — 1 warning, 1 info
  - added the new `unprocessable_entity` enum value to the `errors/items/code` response property for the response status `207`
  - added the optional property `data/items/suppressed` to the response with the `207` status
- …earlier changes not shown

[Full history](https://skmtc.dev/team-telnyx/apis/telnyx-api-2/changes/email_messages/batch/post.md)

---

[API](https://skmtc.dev/team-telnyx/apis/telnyx-api-2.md) · [All operations](https://skmtc.dev/team-telnyx/apis/telnyx-api-2/llms.txt) · [OpenAPI document](https://skmtc.dev/team-telnyx/apis/telnyx-api-2/revisions/88481995b4ca?raw)
