openapi: 3.2.0
info:
title: Bird Email Messages API
version: 1.0.0
description: 'The Bird API: one REST API for email, SMS, WhatsApp, verification, and
Realtime.'
servers:
- url: https://{region}.platform.bird.com
description: 'Regional API endpoint. Use the host for the region your organization is hosted in. Official Bird SDKs and the CLI select it automatically from your API key, so you rarely need to set it by hand.
'
variables:
region:
default: us1
enum:
- us1
- eu1
description: The region your organization's data is hosted in.
- url: https://platform.bird.com
description: Region-independent endpoint for authentication and account administration.
- url: http://localhost:8080
description: Local development.
security:
- BearerAuth: []
tags:
- name: email-messages
description: Send emails to recipients you address explicitly in `to`, `cc`, and `bcc`. Use this for transactional sends (receipts, password resets, alerts) and for marketing sends where you already have the recipient addresses on hand. The same endpoint accepts every content type. Set `category` to control suppression policy.
paths:
/v1/email/messages:
post:
operationId: createEmailMessage
summary: Create an email message
description: 'Sends an email to the recipients you list explicitly in `to`/`cc`/`bcc`. Use it for
transactional sends (receipts, password resets, alerts) and for marketing sends where
you have the recipient addresses on hand. To submit many independent messages in one
request, use Create a batch of email messages
instead. The `category` field controls suppression policy independently of content:
set it to `marketing` when sending marketing content.
The `202` response means the message is safely accepted and awaiting delivery.
Fetch it by `id` or subscribe to webhook events to follow delivery. The
request never half-succeeds: an unverified sender domain or any field-level
validation failure rejects it immediately with a `422` naming the reason.
Suppression is evaluated per recipient after acceptance, so a suppressed recipient
appears as `rejected` on the message''s recipient list rather than as a synchronous
error. New workspaces can send from the shared onboarding domain before verifying
their own. The quickstart covers its
recipient and volume limits.'
tags:
- email-messages
x-audiences:
- public
- command
x-snippet-key: email.send
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessageSendRequest'
examples:
quickstart-first-email:
summary: The smallest send that works, from the getting-started guide
value:
from:
email: onboarding@messagebird.dev
name: Bird
to:
- email: delivered@messagebird.dev
subject: Hello from Bird
html:
My first Bird email.
send-minimal:
summary: 'A minimal send: one sender, one recipient, one body'
value:
from:
email: hello@yourdomain.com
to:
- email: delivered@messagebird.dev
subject: Hello from Bird
html: It works.
send-template:
summary: A send that renders a stored template with parameters
value:
from:
email: hello@yourdomain.com
to:
- email: delivered@messagebird.dev
category: transactional
template:
slug: welcome-email
parameters:
first_name: Jane
send-from-ip-pool:
summary: A send routed through a named dedicated IP pool
value:
from:
email: noreply@yourdomain.com
to:
- email: delivered@messagebird.dev
subject: Your receipt
html: Thanks for your order.
ip_pool_id: ipp_1btmn1nnkd8y6a4jckbkvvt9eh
send-sandbox-bounce:
summary: A send to the sandbox address that always hard-bounces
value:
from:
email: onboarding@messagebird.dev
to:
- email: bounce+signup-flow@messagebird.dev
subject: Sandbox bounce test
html: This message will hard-bounce.
tags:
- name: flow
value: signup
metadata:
test_run: docs-capture-1
quickstart:
summary: A first send on a new workspace
value:
from:
email: onboarding@messagebird.dev
name: Bird
to:
- delivered@messagebird.dev
subject: Welcome to Bird
html: You are in.
onboarding-email:
summary: The first send from the dashboard's onboarding step
value:
from:
email: onboarding@messagebird.dev
name: Bird
to:
- delivered@messagebird.dev
subject: Hello World
html: You made your first email fly. Congratulations!
responses:
'202':
description: Message accepted for asynchronous delivery.
headers:
Idempotency-Replay:
$ref: '#/components/headers/IdempotencyReplay'
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessage'
example:
id: em_01krdgeqcxet5s7t44vh8rt9mg
from:
email: onboarding@messagebird.dev
name: Bird
to:
- email: delivered@messagebird.dev
subject: Hello from Bird
category: transactional
status: accepted
accepted_count: 1
processed_count: 0
delivered_count: 0
bounced_count: 0
complained_count: 0
deferred_count: 0
rejected_count: 0
open_count: 0
click_count: 0
track_opens: false
track_clicks: false
created_at: '2026-07-01T12:00:00Z'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
x-surfaces:
- attio
- cli
- make
- mcp
- n8n
- sdk
get:
operationId: listEmailMessages
summary: List messages
description: 'Returns the workspace''s sent and scheduled messages, newest first, as a cursor page. Each item has the aggregate delivery `status` and per-state recipient counts. Message bodies are omitted.
Combine filters to narrow the page:
- Delivery status.
- Category.
- Tag.
- An exact `to` or `from` address.
- A `created_after` or `created_before` time window.'
tags:
- email-messages
x-audiences:
- public
- command
x-snippet-key: email.list.iterate
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- $ref: '#/components/parameters/PaginationLimit'
- $ref: '#/components/parameters/StartingAfter'
- $ref: '#/components/parameters/EndingBefore'
- $ref: '#/components/parameters/CreatedAfter'
- $ref: '#/components/parameters/CreatedBefore'
- name: status
in: query
required: false
description: Filter by aggregate delivery status.
schema:
$ref: '#/components/schemas/EmailMessageStatus'
- $ref: '#/components/parameters/TagFilter'
- name: category
in: query
required: false
description: Filter by category.
schema:
$ref: '#/components/schemas/EmailMessageCategory'
- name: to
in: query
required: false
description: 'Filter by recipient address. Exact match against any `to`/`cc`/`bcc` recipient on the message. The address is normalized to lowercase before comparison.
'
schema:
type: string
format: email
example: delivered@messagebird.dev
- name: from
in: query
required: false
description: 'Filter by sender address. Exact match against the message `from` field. The address is normalized to lowercase before comparison.
'
schema:
type: string
format: email
example: noreply@acme.com
responses:
'200':
description: Paginated list of messages.
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessageList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
x-surfaces:
- attio
- cli
- make
- mcp
- n8n
- sdk
/v1/email/batches:
post:
operationId: createEmailMessageBatch
summary: Create a batch of email messages
description: 'Accepts up to 100 independent email messages and queues them for delivery. All items are validated before any are queued: if one fails validation, the entire batch is rejected. Field-level validation failures and business-rule failures, such as sending from a domain that is not verified, both return `422`. An item can set `scheduled_at` to send it later, on the same terms as a single scheduled send, and one batch can mix scheduled and immediate messages. Cancel a scheduled item before it sends with Cancel an email message. Suppression is evaluated per recipient after acceptance, never as a synchronous error. The `202` response returns one entry per message in submission order, each with its own `id` you can use to fetch that message or match it against webhook events. Attachments are allowed per message. Each message must stay within the 20 MB estimated generated message-size cap, and the serialized JSON request body for the whole batch has a hard 20 MB cap.'
tags:
- email-messages
x-audiences:
- public
- command
x-snippet-key: email.sendBatch
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessageBatchRequest'
responses:
'202':
description: Batch accepted for asynchronous delivery.
headers:
Idempotency-Replay:
$ref: '#/components/headers/IdempotencyReplay'
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessageBatchResponse'
example:
data:
- id: em_01krdgeqcxet5s7t44vh8rt9mg
status: accepted
category: marketing
requested_language: pt-BR
resolved_language: pt-BR
template_id: emt_01krdgeqcxet5s7t44vh8rt9mg
template_version_id: emv_01krdgeqcxet5s7t44vh8rt9mg
- id: em_01krdgeqcxet5s7t44vh8rt9mh
status: accepted
category: transactional
scheduled_at: '2026-05-22T09:00:00Z'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/Forbidden'
'413':
$ref: '#/components/responses/PayloadTooLarge'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
x-surfaces:
- cli
- make
- mcp
- n8n
- sdk
/v1/email/messages/{message_id}:
get:
operationId: getEmailMessage
summary: Get a message
description: Returns a single message with its aggregate delivery `status` and per-state recipient counts. The response never includes the `html`/`text` bodies. When content storage is enabled for the send, fetch the stored bodies with Get stored message content. Per-recipient statuses and the event timeline are separate sub-resources.
tags:
- email-messages
x-audiences:
- public
- command
x-snippet-key: email.get
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the message to fetch. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
responses:
'200':
description: Message object.
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessage'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
x-surfaces:
- cli
- make
- mcp
- n8n
- sdk
/v1/email/messages/{message_id}/cancel:
post:
operationId: cancelEmailMessage
summary: Cancel a scheduled message
description: Cancels a message that was scheduled with `scheduled_at` before it sends. Only a message that is still scheduled can be canceled. A message that already started sending, was delivered, or was previously canceled returns a conflict error. The message's status becomes `canceled` and an `email.canceled` webhook event fires. Canceling does not return consumed scheduled-send quota.
tags:
- email-messages
x-audiences:
- public
- command
x-snippet-key: email.cancel
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the scheduled message to cancel, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'204':
description: The message is canceled and no longer eligible to send.
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
x-surfaces:
- cli
- make
- mcp
- n8n
- sdk
/v1/email/messages/{message_id}/recipients:
get:
operationId: listEmailMessageRecipients
summary: List recipients of a message
description: 'Returns the message''s per-recipient delivery state as a cursor page: each entry is
one `to`/`cc`/`bcc` recipient with its role, current `status`, rejection or bounce
detail when delivery failed, and open/click counts. Use it to see which specific
addresses failed when the aggregate message `status` is mixed (for example
`partial_failure`).
This works for every email message, including one a broadcast sent. A broadcast
records one message per recipient, so such a message has exactly one recipient
here: the address its copy went to.'
tags:
- email-messages
x-audiences:
- public
- command
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the message whose recipients to list. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
- $ref: '#/components/parameters/PaginationLimit'
- $ref: '#/components/parameters/StartingAfter'
- $ref: '#/components/parameters/EndingBefore'
responses:
'200':
description: Paginated list of recipients for this message.
content:
application/json:
schema:
$ref: '#/components/schemas/EmailRecipientList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
x-surfaces:
- cli
- make
- mcp
- n8n
/v1/email/messages/{message_id}/events:
get:
operationId: listEmailMessageEvents
summary: List events for a message
description: Returns the message's per-recipient event timeline, oldest first, as a cursor page. Lifecycle, failure, and engagement events interleave as they happen, and engagement events (`email.opened`, `email.clicked`) can repeat per recipient. Filter to one event type with `type`. For each recipient's current state rather than its history, use List recipients of a message.
tags:
- email-messages
x-audiences:
- public
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the message whose timeline to read. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
- $ref: '#/components/parameters/PaginationLimit'
- $ref: '#/components/parameters/StartingAfter'
- $ref: '#/components/parameters/EndingBefore'
- name: type
in: query
required: false
description: Filter by event type, for example `email.bounced` or `email.opened`.
schema:
$ref: '#/components/schemas/EmailEventType'
responses:
'200':
description: Paginated event timeline for this message.
content:
application/json:
schema:
$ref: '#/components/schemas/EmailEventList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/Unprocessable'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
x-surfaces:
- make
- n8n
/v1/email/messages/{message_id}/content:
get:
operationId: getEmailMessageContent
summary: Get stored message content
description: Returns the stored HTML and text bodies for a sent message. Content storage must be enabled for the message. Content is available for up to 30 days after sending. A broadcast stores no body, so a copy the send has recorded always answers `404`, whatever the workspace has configured. A `404` indicates no content was stored for this message. A `425` indicates the content is still being stored and the request can be retried shortly. A `410` indicates the content was stored but has since expired.
tags:
- email-messages
x-audiences:
- public
- command
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the message whose stored content to fetch. A broadcast records one message per recipient, and [List email messages](/docs/api/reference/list-email-messages) returns those copies alongside ordinary sends. For a single send, this is the message's own ID, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
responses:
'200':
description: Stored message content.
content:
application/json:
schema:
$ref: '#/components/schemas/EmailMessageContent'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'410':
$ref: '#/components/responses/Gone'
'422':
$ref: '#/components/responses/Unprocessable'
'425':
$ref: '#/components/responses/TooEarly'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
x-surfaces:
- cli
- make
- mcp
- n8n
/v1/email/messages/{message_id}/attachments/{attachment_id}:
get:
operationId: getEmailMessageAttachment
summary: Get a message attachment
description: Downloads the raw bytes of one attachment from a sent message, returned with the attachment's own content type and a Content-Disposition header that includes its filename. The message must have content storage enabled and the attachment is available for up to 30 days after sending. A `404` indicates the message has no stored content or no attachment with this ID. A `425` indicates the attachment is still being stored and the request can be retried shortly. A `410` indicates the attachment was stored but has since expired.
tags:
- email-messages
x-audiences:
- public
security:
- BearerAuth: []
- CookieAuth: []
parameters:
- name: message_id
in: path
required: true
description: ID of the message the attachment belongs to, from the send response's `id` field.
schema:
$ref: '#/components/schemas/EmailID'
- name: attachment_id
in: path
required: true
description: Attachment ID, as returned in the message's `attachments` list.
schema:
$ref: '#/components/schemas/EmailAttachmentID'
responses:
'200':
description: The raw attachment bytes.
headers:
Content-Disposition:
description: 'Has the attachment''s filename. The value is `attachment` for regular files, or `inline` for inline images referenced from the HTML body.
'
schema:
type: string
content:
application/octet-stream:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'410':
$ref: '#/components/responses/Gone'
'422':
$ref: '#/components/responses/Unprocessable'
'425':
$ref: '#/components/responses/TooEarly'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
'503':
$ref: '#/components/responses/ServiceUnavailable'
x-surfaces:
- make
components:
schemas:
EmailMessageBatchRequest:
type: object
additionalProperties: false
description: Batch of email message send requests.
required:
- messages
properties:
messages:
type: array
items:
$ref: '#/components/schemas/EmailMessageSendRequest'
minItems: 1
maxItems: 100
description: 'Email message send requests, up to 100. All items are validated before any are queued. Attachments are allowed on individual messages. Each message must stay within the 20 MB estimated generated message-size cap. The serialized JSON request body for the batch has a hard 20 MB cap.
'
example:
messages:
- from:
email: noreply@acme.com
name: Acme Support
to:
- email: delivered@messagebird.dev
name: Jane Doe
subject: 'Your receipt for order #1234'
text: Thanks for your purchase! Your receipt is attached.
- from:
email: noreply@acme.com
name: Acme Support
to:
- email: delivered@messagebird.dev
name: John Roe
subject: 'Your receipt for order #1235'
text: Thanks for your purchase! Your receipt is attached.
EmailMessageList:
allOf:
- type: object
required:
- data
properties:
data:
type: array
description: Page of message objects.
items:
$ref: '#/components/schemas/EmailMessage'
- $ref: '#/components/schemas/_ListEnvelope'
EmailMessageSendRequest:
type: object
additionalProperties: false
required:
- from
- to
properties:
from:
$ref: '#/components/schemas/EmailAddressInput'
description: Sender address, as a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. Must be from a verified domain in this workspace.
to:
type: array
items:
$ref: '#/components/schemas/EmailAddressInput'
minItems: 1
maxItems: 50
description: Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name.
cc:
type: array
items:
$ref: '#/components/schemas/EmailAddressInput'
maxItems: 50
description: CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name.
bcc:
type: array
items:
$ref: '#/components/schemas/EmailAddressInput'
maxItems: 50
description: BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name.
subject:
type: string
minLength: 1
maxLength: 998
description: Message subject line. Required for inline sends. Omit it when sending a `template` (the template supplies the subject).
html:
type: string
maxLength: 524288
description: HTML body. At least one of html or text must be provided.
text:
type: string
maxLength: 524288
description: Plain-text body. At least one of html or text must be provided.
reply_to:
type: array
items:
$ref: '#/components/schemas/EmailAddressInput'
minItems: 1
maxItems: 25
description: 'Reply-To addresses, each a plain email string, an RFC 5322 mailbox string, or an object with an optional display name. RFC 5322 allows multiple. Every recipient reply hits all listed addresses, so 1-2 is typical. The 25 cap exists to prevent header sizes that some receiving mail servers reject.
'
headers:
type: object
maxProperties: 25
additionalProperties:
type: string
maxLength: 998
description: 'Custom email headers as key-value pairs (for example `References`, `In-Reply-To`, or your own `X-*` headers). Reserved headers are rejected with a `422`. Set the message''s addressing and subject through the dedicated fields: `from`, `to`, `cc`, `bcc`, `reply_to`, and `subject`. The API automatically generates `Content-Type`, `Content-Transfer-Encoding`, `DKIM-Signature`, `Received`, and `Return-Path`. You cannot override these generated headers. `List-Unsubscribe` and `List-Unsubscribe-Post` are honored as-is on `transactional` sends. Marketing sends receive a compliant unsubscribe header, so supplying either one is rejected with a `422`. Header values may not contain carriage-return or line-feed characters. Up to 25 headers per send, each value up to 998 characters.
'
tags:
type: array
items:
$ref: '#/components/schemas/Tag'
maxItems: 20
description: 'Structured `{name, value}` labels for **filtering and analytics**. Tags become first-class query dimensions:
- Filter the list endpoint by tag name.
- Slice analytics rollups by tag.
- Surface in webhook payloads.
Cap: 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`, `template_id`). For arbitrary structured context that you do not need as a filter dimension, use `metadata` instead.
'
metadata:
type: object
description: 'Arbitrary JSON object returned on API reads and included in webhook payloads. You can query its paths in analytics, such as `metadata.order_id`, but it is not a dashboard filter. The serialized object is limited to 2 KB. Use metadata for per-send context such as order IDs, customer references, and structured event data. For low-cardinality filterable labels, use `tags` instead.
'
additionalProperties: true
parameters:
type: object
description: 'Parameter values used to personalize inline content, shared across all recipients of this send. Tokens such as `{{ animal }}` are replaced with matching values; missing values render empty. Include this object, even as `{}`, to use Liquid, or omit it to leave tokens unchanged. Use single-word names other than `bird`. Cap: 16 KB serialized. For a stored template, use `template.parameters` instead. See [inline personalization](https://bird.com/docs/guides/email/sending-email#content) for validation and URL encoding examples.
'
additionalProperties: true
template:
allOf:
- $ref: '#/components/schemas/EmailTemplateSend'
description: 'Send a stored template instead of inline content. When set, omit `subject`, `html` and `text`, because the template supplies them. Personalize with `template.parameters`. A template send goes out immediately: `template` and `scheduled_at` are mutually exclusive, and combining them is rejected with a `422`.
'
track_opens:
type: boolean
default: true
description: Whether to track open events for this message.
track_clicks:
type: boolean
default: true
description: Whether to track click events for this message.
ip_pool_id:
type: string
pattern: ^ipp_([0-9a-hjkmnp-tv-z]{26}|shared)$
description: 'ID of the IP pool to send from (`ipp_` prefix), or `ipp_shared` to route through the shared pool explicitly. Omit to use your organization''s default pool. An unknown pool, or a pool with no dedicated IPs available to send from, is rejected with a `422`.
'
category:
$ref: '#/components/schemas/EmailMessageCategory'
default: marketing
description: 'Content classification, which controls suppression policy:
- `marketing`: Blocks on all suppression reasons.
- `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail.
When you send with `template` and omit this field, the message takes the template''s own classification, so a template created as `transactional` sends as transactional. Set this field to classify a single send differently from its template. It always takes precedence. A send with no template and no category defaults to `marketing`.
'
attachments:
type: array
items:
$ref: '#/components/schemas/EmailAttachment'
maxItems: 20
description: 'Files to attach, up to 20 per message. A message can be at most 20 MB once it has been generated, and we refuse a send that would go over. That figure covers the HTML body, the text body and every attachment and inline image, all measured after base64 encoding, which adds roughly a third. So 15 MB of raw files already accounts for most of the budget, and the body competes for the same space. A batch send is held to the same 20 MB per message, and the whole request body is capped at 20 MB as well.
'
scheduled_at:
type: string
format: date-time
description: 'Schedule the message to send at a future time instead of immediately. Must be at least 30 seconds and at most 30 days ahead. Outside that range the request is rejected with `422`. The message returns with status `accepted` and shows as `scheduled` on reads until it sends. Cancel it before then with the message cancel endpoint. Scheduled sends count against your plan''s monthly scheduled-email allowance. Exceeding it is rejected with a `422`. A scheduled message has inline content: `scheduled_at` and `template` are mutually exclusive, and combining them is rejected with a `422`. Batch items take this field too, so one batch can mix scheduled and immediate messages.
'
example:
from:
email: noreply@acme.com
name: Acme Support
to:
- email: delivered@messagebird.dev
name: Jane Doe
cc:
- manager@acme.com
reply_to:
- support@acme.com
subject: Welcome aboard
html: Hi there 👋
text: Hi there
headers:
X-Campaign: spring-2026
tags:
- name: category
value: welcome
metadata:
user_id: usr_12345
category: transactional
track_clicks: false
ErrorBody:
type: object
additionalProperties: false
required:
- type
- code
- name
- message
- doc_url
- request_id
properties:
type:
type: string
minLength: 1
description: Broad category for coarse client branching.
enum:
- auth_error
- bad_request_error
- billing_error
- conflict_error
- gone_error
- internal_error
- misdirected_error
- not_found_error
- not_implemented_error
- payload_too_large_error
- permission_error
- precondition_error
- rate_limit_error
- service_unavailable_error
- too_early_error
- validation_error
code:
type: string
minLength: 1
pattern: ^E\d{5}$
description: Opaque, stable, unique error identifier. Never reused.
name:
type: string
minLength: 1
description: Human-readable slug for log readability. Paired with code, never replaces it.
message:
type: string
minLength: 1
description: Human-readable description. Not stable; clients must not parse it.
param:
type: string
minLength: 1
description: Identifies the offending field. Omitted when not applicable.
doc_url:
type: string
minLength: 1
format: uri
description: Stable link to the docs page for this error code.
request_id:
type: string
minLength: 1
description: Request correlation ID for support and troubleshooting. Also returned in the `X-Request-Id` response header.
vendor_code:
type: string
minLength: 1
description: 'Verbatim error code from an external system, such as an SMTP response code or a payment decline code. Present only when the code may help you resolve the error.
'
details:
type: array
description: Per-field validation errors. Present only on validation_error responses.
items:
$ref: '#/components/schemas/ErrorDetail'
remediation:
type: string
minLength: 1
description: A human-readable next step to resolve this error. Present when a recovery is known.
next:
type: array
description: 'The steps that resolve this error. Perform them in order, re-reading after each; a `wait` or `terminal` step is always last. Present for errors with a well-defined recovery, such as unmet preconditions and conflicts.
'
items:
$ref: '#/components/schemas/NextAction'
RecipientID:
type: string
minLength: 1
pattern: ^er_[0-9a-hjkmnp-tv-z]{26}$
example: er_01krdgeqcxet5s7t44vh8rt9mg
EmailEventList:
allOf:
- type: object
required:
- data
properties:
data:
type: array
description: Page of timeline events for this email send, in chronological order.
items:
$ref: '#/components/schemas/EmailEvent'
next:
type: array
readOnly: true
description: 'What to do next, given what this page reports. Present only where the read computes it: an
empty list means the answer you were looking for is here and there is nothing further to
do. Absent entirely on reads that do not report next actions.
'
items:
$ref: '#/components/schemas/NextAction'
- $ref: '#/components/schemas/_ListEnvelope'
LanguageTag:
type: string
minLength: 2
maxLength: 35
description: A language tag in BCP-47 form, for example `en` or `pt-BR`.
example: pt-BR
EmailBroadcastID:
type: string
minLength: 1
pattern: ^eb_[0-9a-hjkmnp-tv-z]{26}$
example: eb_01krdgeqcxet5s7t44vh8rt9mg
EmailMessageStatus:
type: string
minLength: 1
enum:
- scheduled
- accepted
- processed
- deferred
- delivered
- partial_failure
- bounced
- complained
- rejected
- canceled
description: 'Aggregate delivery status of an email, derived from its recipients'' states.
In flight:
- `scheduled`: The message is queued to send at a future time and has not been dispatched yet.
- `accepted`: The initial status of an immediate send. The message is queued for its recipients.
- `processed`: Delivery is underway, so at least one recipient''s message is on its way out and none has failed.
- `deferred`: At least one recipient''s mailbox provider asked for a retry, and delivery attempts continue.
Final:
- `delivered`: Every recipient''s mail server accepted the message.
- `bounced`: Every recipient permanently failed (bounced or was rejected).
- `rejected`: Every recipient was rejected before a delivery attempt (for example, all recipients were suppressed).
- `partial_failure`: Some recipients permanently failed while others were delivered or are still in flight.
- `canceled`: A scheduled message was canceled before it was sent.
`complained` takes precedence over every other status: at least one recipient reported
the message as spam, regardless of what happened to the rest.
'
EmailTemplateID:
type: string
minLength: 1
pattern: ^emt_[0-9a-hjkmnp-tv-z]{26}$
example: emt_01krdgeqcxet5s7t44vh8rt9mg
EmailMessageBatchResponse:
type: object
additionalProperties: false
required:
- data
properties:
data:
type: array
items:
$ref: '#/components/schemas/EmailMessageBatchItem'
description: One entry per message in the batch, in submission order.
EmailEventType:
type: string
minLength: 1
description: 'Type of an event in a message''s per-recipient delivery timeline.
- `email.scheduled`: We accepted a send scheduled for a future time. Fires once for each message regardless of its recipient count.
- `email.accepted`: We accepted the send and are getting ready to deliver it. Fires once per requested recipient.
- `email.processed`: We queued the message for delivery to the recipient''s mail server.
- `email.deferred`: The recipient''s mail server temporarily refused the message. Delivery remains pending and is retried. Can fire more than once per recipient.
- `email.delivered`: The recipient''s mail server accepted the message.
- `email.bounced`: Delivery permanently failed at the recipient''s mail server.
- `email.out_of_band_bounce`: A bounce notification arrived after the message had already been accepted for delivery.
- `email.rejected`: We rejected the message before attempting delivery, for example because the recipient is suppressed.
- `email.canceled`: A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count.
- `email.opened`: The recipient opened the message. Can fire more than once per recipient.
- `email.clicked`: The recipient clicked a tracked link in the message. Can fire more than once per recipient.
- `email.unsubscribed`: The recipient opted out through a tracked unsubscribe link in the message.
- `email.list_unsubscribed`: The recipient opted out through the one-click unsubscribe control in their mail client.
- `email.complained`: The recipient reported the message as spam through their mailbox provider.
We can add new event types to this list over time, so treat a value you do not recognize as a new type rather than as an error.
'
x-extensible-enum:
- email.accepted
- email.bounced
- email.canceled
- email.clicked
- email.complained
- email.deferred
- email.delivered
- email.list_unsubscribed
- email.opened
- email.out_of_band_bounce
- email.processed
- email.rejected
- email.scheduled
- email.unsubscribed
example: email.delivered
EmailTemplateVersionID:
type: string
minLength: 1
pattern: ^emv_[0-9a-hjkmnp-tv-z]{26}$
example: emv_01krdgeqcxet5s7t44vh8rt9mg
EmailTemplateSend:
type: object
additionalProperties: false
description: 'A reference to the template to send. Identify the template by its `id` or its `slug`, supplying exactly one of the two, and give the values for its variables in `parameters`.
'
oneOf:
- required:
- id
- required:
- slug
properties:
id:
description: The template to send, by its id.
$ref: '#/components/schemas/EmailTemplateID'
slug:
allOf:
- $ref: '#/components/schemas/TemplateSlug'
description: The template to send, by its slug handle. A workspace template (for example `welcome-email`) or a built-in `system` template (for example `bird_welcome`).
example: welcome-email
language:
$ref: '#/components/schemas/LanguageTag'
description: 'Which of the template''s languages to send. Omit it to send the template''s default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not have the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected.
'
parameters:
type: object
additionalProperties: true
description: 'Values for the template''s variables, keyed by the variable name. A variable name is a single word.
Every variable in the template''s `variables` list needs a value. A send
that omits one is rejected. Languages can use different variables, and a
value unused by the selected language is ignored.
The API supplies values under the reserved `bird` key, so a send that sets
it is rejected. `parameters` is capped at 16 KB once serialized.
'
example:
animal: otter
EmailMessageCategory:
type: string
minLength: 1
enum:
- marketing
- transactional
description: 'Content classification, which controls suppression policy:
- `marketing`: Blocks on all suppression reasons.
- `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail.
'
_ListEnvelope:
type: object
required:
- next_cursor
- prev_cursor
- refresh_cursor
properties:
next_cursor:
type:
- string
- 'null'
description: Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
example: eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9
prev_cursor:
type:
- string
- 'null'
description: Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
example: null
refresh_cursor:
type:
- string
- 'null'
description: Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.
example: eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9
EmailEvent:
type: object
additionalProperties: false
required:
- id
- type
- occurred_at
- recipient_id
properties:
id:
type: string
minLength: 1
readOnly: true
pattern: ^ev_[0-9a-hjkmnp-tv-z]{26}$
description: Event ID.
example: ev_01krdgeqcxet5s7t44vh8rt9mg
type:
$ref: '#/components/schemas/EmailEventType'
description: 'The event''s type. `email.processed`, for example, means the message has been processed and queued for delivery.
'
occurred_at:
type: string
format: date-time
minLength: 1
description: When this event occurred.
recipient_id:
$ref: '#/components/schemas/RecipientID'
description: Recipient this event applies to.
bounce_type:
type:
- string
- 'null'
enum:
- hard
- soft
- undetermined
- admin
- block
- null
description: 'Bounce classification. Present on `email.bounced`, `email.out_of_band_bounce`, and
`email.deferred` events.
- `hard`: a permanent failure (invalid address or non-existent domain).
- `soft`: a transient failure (mailbox full or server temporarily unavailable).
- `block`: the receiving mail server blocked the sending IP for reputation reasons.
- `admin`: an administrative refusal (relaying denied or blocklisted domain).
- `undetermined`: the receiving server''s response is ambiguous.
'
bounce_class:
type:
- integer
- 'null'
minimum: 1
maximum: 255
description: 'A more detailed numeric bounce code, useful for telling apart failures that share the same `bounce_type`. For example, a DNS failure and a spam block can both come through as `bounce_type: soft` or `bounce_type: block`; this field tells you which one actually happened. Present on `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events.
'
bounce_code:
type:
- string
- 'null'
description: 'SMTP status code returned by the receiving mail server. Present on `email.bounced` and `email.deferred` events.
'
example: 5.1.1
bounce_description:
type:
- string
- 'null'
description: The bounce reason, in plain language, as reported by the mail server. Present on `email.bounced` and `email.deferred` events.
rejection_reason:
type:
- string
- 'null'
enum:
- recipient_suppressed
- transmission_failed
- generation_failure
- policy_rejection
- domain_unverified
- quota_exceeded
- recipient_not_allowed
- null
description: 'Specific cause of rejection. Present on `email.rejected` events only.
- `recipient_suppressed`: The recipient is on the workspace suppression list.
- `transmission_failed`: The message could not be transmitted for delivery.
- `generation_failure`: The message could not be built for delivery, because of a template or content issue.
- `policy_rejection`: The message was refused by sending policy.
- `domain_unverified`: The sending domain was not verified.
- `quota_exceeded`: The organization''s send quota was reached.
- `recipient_not_allowed`: This recipient was not allowed for this send. For a send from the shared onboarding domain, every recipient has to be a verified member of the workspace.
'
sending_ip:
type:
- string
- 'null'
description: 'The IP address used to send this message. Useful for spotting a deliverability problem that is tied to one specific sending IP rather than affecting all of them. Present on `email.delivered`, `email.bounced`, `email.out_of_band_bounce`, and `email.deferred` events.
'
mailbox_provider:
type:
- string
- 'null'
description: 'The recipient mailbox provider, as a lowercased classifier bucket (e.g. `gmail`, `yahoo`, `microsoft`, `apple`). Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when the receiving mail system could be classified; null when it could not.
'
mailbox_provider_region:
type:
- string
- 'null'
description: 'The provider region, as reported by the receiving mail system (for example `NA`, `EU`, `APAC`). The set is open and provider-specific. Present on `email.processed`, `email.delivered`, `email.complained`, `email.bounced`, `email.out_of_band_bounce`, `email.deferred`, `email.rejected`, `email.unsubscribed`, `email.list_unsubscribed`, `email.opened`, and `email.clicked` events when reported; null otherwise.
'
is_prefetched:
type:
- boolean
- 'null'
description: 'True when the open was auto-fetched by an inbox privacy feature (Apple Mail Privacy Protection, the Gmail image proxy) rather than a person actually opening the message. Use it to calculate open rate accurately. Present on `email.opened` events only.
'
url:
type:
- string
- 'null'
description: The clicked URL. Present on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link in the message.
link_name:
type:
- string
- 'null'
description: The clicked link's own name, when the link in the message carried one, so a click can be reported by what the link said rather than where it pointed. Absent when the link had no name. Appears alongside `url` on `email.clicked` events, and on `email.unsubscribed` events when the recipient unsubscribed through a link.
example: Faster exports, docs
country:
type:
- string
- 'null'
minLength: 2
maxLength: 2
description: ISO 3166-1 alpha-2 country code derived from the client IP. Present on `email.opened` and `email.clicked` events when available.
example: US
ip_address:
type:
- string
- 'null'
description: Client IP address (IPv4 or IPv6). Present on `email.opened` and `email.clicked` events when available.
user_agent:
type:
- string
- 'null'
description: Client user-agent string. Present on `email.opened` and `email.clicked` events when available.
ErrorDetail:
type: object
additionalProperties: false
required:
- param
- message
properties:
param:
type: string
minLength: 1
description: 'Dotted field path, such as `to[0].email`, `subject`, or `.`. When the request was rejected for a query parameter the endpoint does not declare, this carries that parameter''s name instead of a field path.
'
message:
type: string
minLength: 1
description: What is wrong with this field.
EmailAddressInput:
description: 'A sender or recipient address. Accepts a plain email string (`jane@acme.com`), an RFC 5322 mailbox string with an embedded display name (`Jane Doe `), or an object carrying the address and an optional display name. All forms can be mixed freely within one request. Responses always return the object form.
'
oneOf:
- type: string
minLength: 5
maxLength: 998
pattern: ^[^\r\n]+$
title: Email string
description: Email address, optionally in RFC 5322 mailbox form with an embedded display name.
example: Jane Doe
- $ref: '#/components/schemas/EmailAddress'
Tag:
type: object
additionalProperties: false
required:
- name
- value
description: 'Structured key/value label attached to a message or a call. Use tags for low-cardinality filtering dimensions (category, experiment ID, template ID); they surface in the list filter of whatever carries them.
On a message they also surface in the event log and in webhook payloads, and a message can carry `metadata` beside them for arbitrary per-send context that does not need to be filterable. A call has none of those three: its tags are set on the wire when the call is placed, and the call record is the one place you read them back.
Whatever carries the tags defines how many it may have. Tag names are unique: a send that repeats one is rejected, and on a call the first instance of a name wins.
'
properties:
name:
type: string
minLength: 1
maxLength: 32
pattern: ^[A-Za-z0-9_-]+$
description: 'Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
'
example: category
value:
type: string
minLength: 1
maxLength: 64
pattern: ^[A-Za-z0-9_-]+$
description: 'Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
'
example: welcome
EmailAttachmentRef:
type: object
additionalProperties: false
required:
- filename
- size
description: 'Attachment metadata returned on API reads. Download the file during its retention window with `GET /v1/email/messages/{message_id}/attachments/{attachment_id}`.
'
properties:
id:
readOnly: true
description: Attachment ID, stable per email send.
$ref: '#/components/schemas/EmailAttachmentID'
filename:
type: string
minLength: 1
description: Filename as shown to the recipient.
example: invoice.pdf
content_type:
type: string
description: Resolved MIME type at send time.
example: application/pdf
size:
type: integer
minimum: 0
description: Decoded size in bytes.
example: 215432
inline:
type: boolean
description: 'True when the attachment was sent inline via a `content_id` reference in the HTML body, false for regular file attachments.
'
example: false
content_id:
type:
- string
- 'null'
description: The Content-ID set at send time, when the attachment was inline.
NextAction:
type: object
additionalProperties: false
required:
- kind
- description
properties:
kind:
type: string
minLength: 1
x-extensible-enum:
- operation
- external
- wait
- terminal
description: "What you do about this step.\n\n- `operation`: call the operation named in `operation`, then\n read again.\n- `external`: act somewhere this API does not reach, then read\n again.\n- `wait`: nothing is asked of you, so read again later.\n- `terminal`: nothing you do resolves this, so stop retrying.\n\nTolerate a value you do not recognize: show the `description` and\noffer no action.\n"
description:
type: string
minLength: 1
description: A short, human-readable label for the step, suitable for display.
operation:
type: string
minLength: 1
description: 'The operationId to call. Present only when `kind` is `operation`. The operation''s own schema says how to call it; this says only which one, and what to address it with.
'
params:
type: object
additionalProperties:
type: string
minLength: 1
description: 'The parameters that address the operation, by name: `{"sender_id": "…"}` for an operation on `/v1/sms/senders/{sender_id}/requirements`. A parameter the operation takes in its query string is given the same way, so an operation addressed as `?subject_id=` carries `{"subject_id": "…"}`. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when `kind` is `operation` and the operation names a subject. A request body, when the operation takes one, is described by the operation''s own schema and never appears here.
'
url:
type: string
format: uri
description: 'A URL to open. Present only when `kind` is `external`, and only when the step has one. An external step whose `description` says to go and do something with no URL to open is normal.
'
EmailRecipientList:
allOf:
- type: object
required:
- data
properties:
data:
type: array
description: Page of recipient objects for this email send.
items:
$ref: '#/components/schemas/EmailRecipient'
next:
type: array
readOnly: true
description: 'What to do next, given what this page reports. Present only where the read computes it: an
empty list means the answer you were looking for is here and there is nothing further to
do. Absent entirely on reads that do not report next actions.
'
items:
$ref: '#/components/schemas/NextAction'
- $ref: '#/components/schemas/_ListEnvelope'
Error:
type: object
additionalProperties: false
required:
- error
properties:
error:
$ref: '#/components/schemas/ErrorBody'
EmailID:
type: string
minLength: 1
pattern: ^em_[0-9a-hjkmnp-tv-z]{26}$
example: em_01krdgeqcxet5s7t44vh8rt9mg
EmailMessageContent:
type: object
additionalProperties: false
description: 'The body content of a sent email message, as delivered. A send that used a template stores the template''s body, so these report it with the send''s `parameters` substituted in.
'
properties:
html:
type: string
minLength: 1
description: The HTML body of the message as delivered, if it was stored.
text:
type: string
minLength: 1
description: The plain-text body of the message as delivered, if it was stored.
EmailRecipient:
type: object
additionalProperties: false
required:
- id
- parent_id
- role
- recipient
- status
- open_count
- click_count
properties:
id:
readOnly: true
$ref: '#/components/schemas/RecipientID'
description: Recipient ID.
parent_id:
$ref: '#/components/schemas/EmailID'
description: ID of the message this recipient belongs to. For a message send, this is the message's own `em_`-prefixed ID. For a broadcast, it is the `em_`-prefixed ID of the copy addressed to this recipient. Read either one with [Get an email message](/docs/api/reference/get-email-message), which answers 404 for a broadcast copy the send has not recorded. No recipient status distinguishes a copy the send recorded from one it did not.
role:
$ref: '#/components/schemas/RecipientRole'
description: How this recipient appeared in the send request.
recipient:
type: string
format: email
minLength: 5
description: Recipient email address.
name:
type:
- string
- 'null'
description: Display name provided for this recipient on the send, or null if none was given.
status:
type: string
minLength: 1
readOnly: true
enum:
- accepted
- processed
- deferred
- delivered
- bounced
- complained
- rejected
x-enum-varnames:
- EmailRecipientStatusAccepted
- EmailRecipientStatusProcessed
- EmailRecipientStatusDeferred
- EmailRecipientStatusDelivered
- EmailRecipientStatusBounced
- EmailRecipientStatusComplained
- EmailRecipientStatusRejected
description: 'Delivery status for this recipient:
- `accepted`: The send has been taken and is being prepared for delivery.
- `processed`: This recipient''s message is on its way out.
- `deferred`: The recipient''s mailbox provider asked for a retry, and delivery attempts continue.
- `delivered`: The recipient''s mail server accepted the message.
- `bounced`: Delivery permanently failed (see `bounce_type` for hard vs soft).
- `complained`: The recipient reported the message as spam.
- `rejected`: Delivery was never attempted (see `rejection_reason` for why).
'
rejection_reason:
type:
- string
- 'null'
readOnly: true
enum:
- recipient_suppressed
- transmission_failed
- generation_failure
- policy_rejection
- domain_unverified
- quota_exceeded
- recipient_not_allowed
- null
description: "Present on `status: rejected` rows. Specifies why the recipient was rejected:\n\n- `recipient_suppressed`: The recipient is on the workspace suppression list, so\n delivery was never attempted.\n- `transmission_failed`: The message could not be transmitted for delivery.\n- `generation_failure`: The message could not be built for delivery (template or\n content issue).\n- `policy_rejection`: The message was refused by sending policy.\n- `domain_unverified`: The sending domain was not verified.\n- `quota_exceeded`: The organization's send quota was reached.\n- `recipient_not_allowed`: A recipient was not permitted for this send (for shared\n onboarding-domain sends, recipients must be verified workspace members).\n"
bounce_type:
type:
- string
- 'null'
readOnly: true
enum:
- hard
- soft
- undetermined
- admin
- block
- null
description: 'Bounce classification for `bounced` and `deferred` rows, or null when the recipient
has not bounced or the receiving server''s response has not been classified.
- `hard`: a permanent failure (invalid address or non-existent domain).
- `soft`: a transient failure (mailbox full or server temporarily unavailable).
- `block`: the receiving mail server blocked the sending IP for reputation reasons.
- `admin`: an administrative refusal (relaying denied or blocklisted domain).
- `undetermined`: the receiving server''s response is ambiguous.
'
bounce_code:
type:
- string
- 'null'
readOnly: true
description: SMTP reply code returned by the receiving mail server for `bounced` and `deferred` rows, or null when none was provided.
example: '550'
bounce_description:
type:
- string
- 'null'
readOnly: true
description: Human-readable reason the receiving mail server gave for the bounce or deferral, or null when none was provided.
example: 5.1.1 Unknown user
processed_at:
type:
- string
- 'null'
format: date-time
readOnly: true
description: When the message was prepared and queued for delivery to the recipient's mail server, or null if that has not happened yet.
delivered_at:
type:
- string
- 'null'
format: date-time
readOnly: true
description: When the recipient's mail server accepted the message, or null if not yet delivered.
processing_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: Time between the send being accepted and the message being prepared for delivery, in milliseconds. Null until processed.
delivery_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: Time between the message being prepared and the receiving mail server accepting it, in milliseconds. Null until delivered.
total_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: End-to-end accept → delivered time for this recipient, in milliseconds. Null until delivered.
open_count:
type: integer
readOnly: true
default: 0
description: Number of open events for this recipient.
click_count:
type: integer
readOnly: true
default: 0
description: Number of click events for this recipient.
EmailAttachment:
type: object
additionalProperties: false
required:
- filename
- content
description: 'A file attached to an email. Put the base64-encoded bytes in `content` and the
recipient-facing name in `filename`. To show an image inline, set `content_id`
and reference it from the HTML body with `
`.
The generated message is limited to 20 MB across the HTML body, text body,
attachments, and inline images after base64 and MIME encoding. Keep raw
attachment content at or below 15 MB to leave room for encoding and the body.
Batch sends apply the same 20 MB limit to each message and to the full request
body. Executable and script content types are rejected.
'
properties:
filename:
type: string
minLength: 1
maxLength: 255
description: The name the recipient sees on the attachment.
example: invoice.pdf
content:
type: string
format: byte
minLength: 1
description: Base64-encoded file bytes. The encoded value and MIME wrapping count toward the 20 MB message limit.
content_type:
type: string
description: The file's MIME type. If omitted, the API infers it from the extension in `filename`. The API rejects executable and script types based on this value.
example: application/pdf
content_id:
type: string
minLength: 1
maxLength: 128
pattern: ^[A-Za-z0-9._-]+$
description: An RFC 2392 Content-ID for an inline file. Reference it from the HTML body with `
`. Omit it to send a downloadable attachment.
example: invoice-logo
EmailAttachmentID:
type: string
minLength: 1
pattern: ^ea_[0-9a-hjkmnp-tv-z]{26}$
example: ea_01krdgeqcxet5s7t44vh8rt9mg
RecipientRole:
type: string
minLength: 1
enum:
- to
- cc
- bcc
description: Envelope position of a recipient on an outbound email event.
example: to
TemplateSlug:
type: string
minLength: 1
maxLength: 63
pattern: ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$
description: 'A template''s slug: what you send it by, for example `welcome-email`. Email and SMS slugs stay fixed after creation. WhatsApp slugs can change only before the first submission. A slug can contain lowercase letters, numbers, hyphens, and underscores, has to start and end with a letter or a number, and can be up to 63 characters long.
'
example: welcome-email
EmailMessage:
type: object
description: 'An email message, including a recipient''s copy of a broadcast. `broadcast_id` identifies the broadcast that sent the message and is absent for other sends. A broadcast records one message per recipient; these copies share the same `broadcast_id`.
'
additionalProperties: false
required:
- id
- from
- to
- subject
- category
- status
- accepted_count
- processed_count
- delivered_count
- bounced_count
- complained_count
- deferred_count
- rejected_count
- open_count
- click_count
- track_opens
- track_clicks
- created_at
example:
id: em_01krdgeqcxet5s7t44vh8rt9mg
from:
email: onboarding@messagebird.dev
name: Bird
to:
- email: delivered@messagebird.dev
subject: Hello from Bird
category: marketing
status: accepted
broadcast_id: eb_01krdgeqcxet5s7t44vh8rt9mg
accepted_count: 1
processed_count: 0
delivered_count: 0
bounced_count: 0
complained_count: 0
deferred_count: 0
rejected_count: 0
open_count: 0
click_count: 0
track_opens: false
track_clicks: false
created_at: '2026-07-01T12:00:00Z'
properties:
id:
readOnly: true
$ref: '#/components/schemas/EmailID'
description: Message ID.
from:
$ref: '#/components/schemas/EmailAddress'
description: Sender address. `name` is present when a display name was provided on the send.
to:
type: array
items:
$ref: '#/components/schemas/EmailAddress'
minItems: 1
maxItems: 50
description: Primary recipients. Length is the recipient count. Use the broadcasts endpoint for audience-targeted sends. Each entry's `name` is present when a display name was provided on the send.
cc:
type: array
items:
$ref: '#/components/schemas/EmailAddress'
description: CC recipients.
bcc:
type: array
items:
$ref: '#/components/schemas/EmailAddress'
description: BCC recipients.
subject:
type: string
minLength: 1
description: 'The subject line as delivered. For a send that used a template, the stored subject is the template''s, so this reports it with the send''s `parameters` substituted in, which is what the recipient saw.
'
category:
$ref: '#/components/schemas/EmailMessageCategory'
reply_to:
type:
- array
- 'null'
items:
$ref: '#/components/schemas/EmailAddress'
maxItems: 25
description: Reply-To addresses, if set on the send. Empty/null when no Reply-To was provided.
status:
readOnly: true
allOf:
- $ref: '#/components/schemas/EmailMessageStatus'
accepted_count:
type: integer
readOnly: true
default: 0
description: How many recipients are in the `accepted` state, meaning we have the message and are getting ready to deliver it.
processed_count:
type: integer
readOnly: true
default: 0
description: How many recipients the message has been prepared for and queued for delivery.
delivered_count:
type: integer
readOnly: true
default: 0
description: How many recipients' messages were accepted by their mail server.
bounced_count:
type: integer
readOnly: true
default: 0
description: Number of recipients that resulted in a permanent delivery failure.
complained_count:
type: integer
readOnly: true
default: 0
description: Number of recipients that reported spam.
deferred_count:
type: integer
readOnly: true
default: 0
description: Number of recipients in transient delivery deferral. Their mail server asked for a retry, and delivery attempts continue.
rejected_count:
type: integer
readOnly: true
default: 0
description: 'Number of recipients rejected before delivery. Read the per-recipient `rejection_reason` field on `GET /v1/email/messages/{message_id}/recipients` for the specific cause.
'
processing_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: 'Time between the send being accepted and the message being prepared for delivery, in milliseconds, for the fastest recipient. Null until the first recipient reaches `processed`.
'
delivery_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: 'Time between the message being processed and the receiving mail server accepting it, in milliseconds, for the fastest delivered recipient. Null until the first recipient is delivered.
'
total_latency_ms:
type:
- integer
- 'null'
minimum: 0
readOnly: true
description: 'End-to-end accept → delivered time for the fastest delivered recipient, in milliseconds. Null until the first recipient is delivered.
'
open_count:
type: integer
readOnly: true
default: 0
description: Total open events across all recipients.
click_count:
type: integer
readOnly: true
default: 0
description: Total click events across all recipients.
requested_language:
oneOf:
- $ref: '#/components/schemas/LanguageTag'
- type: 'null'
readOnly: true
description: 'The template language this send asked for, in canonical form (`pt-BR` for a request of `pt-br`). Null when the send named no language (it took the template''s default) or used no template at all. Compare it with `resolved_language`: when they differ, the language you asked for was not available and the template''s `on_missing_language` policy chose the one shown there instead.
'
resolved_language:
oneOf:
- $ref: '#/components/schemas/LanguageTag'
- type: 'null'
readOnly: true
description: 'The template language this send was actually delivered in, in canonical form. Null when the send used no template. A non-null value with a null `requested_language` means the send named no language and took the template''s default.
'
template_id:
oneOf:
- $ref: '#/components/schemas/EmailTemplateID'
- type: 'null'
readOnly: true
description: 'The template this send rendered from, or null for a send that supplied its content inline.
'
template_version_id:
oneOf:
- $ref: '#/components/schemas/EmailTemplateVersionID'
- type: 'null'
readOnly: true
description: 'The exact template version this send rendered from, or null for an inline send. A template''s live version changes every time you submit it, so this is what identifies the wording that was actually delivered, together with `resolved_language`.
'
broadcast_id:
$ref: '#/components/schemas/EmailBroadcastID'
readOnly: true
description: 'The broadcast that sent this message. Absent for a send that was not part of a broadcast. A broadcast records one message per recipient, and every one of them carries the same value here.
'
tags:
type: array
items:
$ref: '#/components/schemas/Tag'
description: Labels on this message, each one a `name` and a `value`, that you can filter and search messages by. Use tags for anything you want to find messages by later, and `metadata` for data you only want handed back to you.
metadata:
type: object
description: Any JSON you kept on the message. We store it and hand it back in webhook payloads, and that is all it does. If you want to search or filter by it, use `tags` instead.
additionalProperties: true
parameters:
type:
- object
- 'null'
additionalProperties: true
readOnly: true
description: 'The substitution values this send supplied, whether inline or from a template, or null if none were supplied. They are the values applied to `subject` and to the bodies the content endpoint returns, kept so you can see what produced the delivered copy and not only the result.
'
attachments:
type: array
items:
$ref: '#/components/schemas/EmailAttachmentRef'
description: Attachment metadata for the send. Empty when no attachments were included. Raw content is not echoed. When content storage is enabled, download an attachment by its `id` via the message's attachment endpoint.
track_opens:
type: boolean
description: Whether open tracking is enabled for this send.
track_clicks:
type: boolean
description: Whether click tracking is enabled for this send.
created_at:
type: string
format: date-time
minLength: 1
readOnly: true
description: When the send request was accepted.
thread_id:
type:
- string
- 'null'
readOnly: true
pattern: ^thr_[0-9a-hjkmnp-tv-z]{26}$
description: Thread this message belongs to, or null when the message is not part of one.
in_reply_to_message_id:
readOnly: true
description: The message this one is a reply to, if any.
oneOf:
- $ref: '#/components/schemas/EmailID'
- type: 'null'
delivered_at:
type:
- string
- 'null'
format: date-time
readOnly: true
description: When all recipients reached a terminal delivered state, or null if not yet fully delivered.
scheduled_at:
type: string
format: date-time
readOnly: true
description: When this message is scheduled to send, for a send created with a future send time. Absent for an immediate send. Stays set after the scheduled send fires.
EmailAddress:
type: object
additionalProperties: false
description: An email address with an optional display name.
required:
- email
properties:
email:
type: string
format: email
minLength: 5
description: Email address.
example: jane@acme.com
name:
type: string
minLength: 1
maxLength: 256
pattern: ^[^\r\n]+$
description: Display name shown alongside the address in mail clients.
example: Jane Doe
EmailMessageBatchItem:
type: object
additionalProperties: false
required:
- id
- status
- category
properties:
id:
readOnly: true
$ref: '#/components/schemas/EmailID'
description: Message ID assigned to this batch item.
status:
type: string
minLength: 1
readOnly: true
enum:
- accepted
description: Initial status of this message in the batch.
category:
type: string
minLength: 1
enum:
- marketing
- transactional
description: Resolved category for this batch item.
requested_language:
oneOf:
- $ref: '#/components/schemas/LanguageTag'
- type: 'null'
readOnly: true
description: 'The template language this item asked for, in canonical form. Null when the item named no language or used no template. Every item in a batch resolves its own template reference, so this and `resolved_language` can differ from item to item.
'
resolved_language:
oneOf:
- $ref: '#/components/schemas/LanguageTag'
- type: 'null'
readOnly: true
description: 'The template language this item was actually delivered in, in canonical form. Null when the item used no template. A value here differing from `requested_language` means the template did not have the language asked for and its `on_missing_language` policy chose this one.
'
template_id:
oneOf:
- $ref: '#/components/schemas/EmailTemplateID'
- type: 'null'
readOnly: true
description: 'The template this item rendered from, or null for an item that supplied its content inline.
'
template_version_id:
oneOf:
- $ref: '#/components/schemas/EmailTemplateVersionID'
- type: 'null'
readOnly: true
description: 'The exact template version this item rendered from, or null for an inline item. Record it if you need to reproduce what was sent: a template''s live version changes every time you submit it.
'
scheduled_at:
type: string
format: date-time
readOnly: true
description: 'When this item is scheduled to send, for an item created with a future send time. Absent for an item that sends immediately.
'
parameters:
CreatedBefore:
name: created_before
in: query
required: false
description: Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset.
schema:
type: string
format: date-time
example: '2026-06-01T00:00:00Z'
CreatedAfter:
name: created_after
in: query
required: false
description: Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset.
schema:
type: string
format: date-time
example: '2026-05-01T00:00:00Z'
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: "Client-supplied key. On operations supporting request deduplication, a retained\nresponse is replayed for duplicate requests with the same key within the\nidempotency window (3 hours by default). This protection requires a workspace,\norganization, or staff-account scope. User-only and unauthenticated operations,\nstreams, and operations with a separate replay contract do not use this\nresponse replay.\n\nOn a supported operation, if idempotency protection is unavailable before execution, the API returns\n`503 IdempotencyUnavailable` (E01033) without executing this attempt. Retry with\nbackoff using the same key and request. An operation that takes effect before\nits response is retained can still execute again on retry.\n\nTwo distinct 409 errors signal misuse:\n\n- `request_in_progress` (E01004): The same key is currently being\n processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds.\n- `idempotency_key_reuse` (E01005): The same key has already completed\n against a different request body or method. Generate a new key.\n\nRecommended key format is `/` (for example `welcome-user/usr_abc123`).\n"
schema:
type: string
maxLength: 255
TagFilter:
name: tag
in: query
required: false
description: 'Filter by tag. Accepts `name` to match any record carrying that tag name, or `name:value` to match a specific tag pair (for example `category:welcome`). Repeat the parameter to add more tags. A record must match every tag listed to be returned.
'
schema:
type: array
items:
type: string
StartingAfter:
name: starting_after
in: query
required: false
description: Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order.
schema:
type: string
EndingBefore:
name: ending_before
in: query
required: false
description: Cursor from the `prev_cursor` or `refresh_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. `prev_cursor` returns the preceding page. `refresh_cursor` anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.
schema:
type: string
PaginationLimit:
name: limit
in: query
required: false
description: Maximum number of items to return per page.
schema:
type: integer
minimum: 1
maximum: 100
default: 25
responses:
Gone:
description: The resource existed but is no longer available.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
InternalError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PaymentRequired:
description: Insufficient balance
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: Rate limit exceeded
headers:
RateLimit:
$ref: '#/components/headers/RateLimit'
RateLimit-Policy:
$ref: '#/components/headers/RateLimit-Policy'
Retry-After:
$ref: '#/components/headers/RetryAfter'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: Insufficient permissions
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
ServiceUnavailable:
description: 'The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.
'
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unprocessable:
description: 'The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
TooEarly:
description: The resource is not available yet. Retry shortly.
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PayloadTooLarge:
description: Request body or message size exceeds the allowed limit
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Authentication required
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Conflict:
description: Resource conflict
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
headers:
RetryAfter:
description: 'Number of seconds to wait before retrying the request.
'
schema:
type: integer
minimum: 0
example: 35
IdempotencyReplay:
description: The API includes this header when it replays the response for an earlier request that used the same `Idempotency-Key`. The API does not process the request again.
schema:
type: string
enum:
- 'true'
RateLimit:
description: 'Remaining capacity for the request''s rate-limit policy as an IETF Structured Field. Format: `"";r=;t=`.
'
schema:
type: string
example: '"email_send";r=842;t=35'
RateLimit-Policy:
description: 'Effective quota for the request''s rate-limit policy as an IETF Structured Field. Format: `"";q=;w=`.
'
schema:
type: string
example: '"email_send";q=1000;w=60'
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: 'Pass the API key as a bearer token in the `Authorization` header. Keys use
the format `bk_{region}_*`. The prefix identifies the region and selects the
API endpoint. Official Bird SDKs and the CLI derive the region from the key.
'
CookieAuth:
type: apiKey
in: cookie
name: bird_session
description: 'Session cookie set after signing in to the Bird dashboard. The cookie
value is an opaque session token; no session data is stored in the cookie
itself.
'
RealtimeKey:
type: apiKey
in: header
name: X-Realtime-Key
description: 'The Realtime app key. Together with `X-Realtime-Secret`, it authenticates a
request to the Realtime API in addition to the workspace credential. Both
values come from the app''s credentials and must belong to the calling
workspace. Official Bird SDKs accept the pair as client configuration.
'
RealtimeSecret:
type: apiKey
in: header
name: X-Realtime-Secret
description: 'The Realtime app secret paired with `X-Realtime-Key`. The API returns the
secret only when the key is created and does not store it. Create a new key
and revoke the current key if you lose the secret. Official Bird SDKs accept
the pair as client configuration.
'