Sign inGet Started

Create an email message

POST
/v1/email/messages
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  subject: "Hello from Bird",
  html: "<p>My first Bird email.</p>",
});
console.log(msg.id, msg.status); // "em_…", "accepted"
Response202
{
  "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"
}

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.

For a published template, supply caller values in template.parameters. Contact IDs are not recipient addresses.

When the selected template language reads contact attributes, Bird looks up each To address in this workspace and saves the referenced values at acceptance. Each To receives its own personalized email; Cc and Bcc receive a copy of each To's email and do not need matching contacts. A To address without a matching contact rejects the request with 422 (E04099), even if Liquid supplies a default. Existing contacts may have empty name fields. This also applies to scheduled sends. Templates without contact attributes keep the usual recipient behavior.

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.

Request Payload

from
string or object
required

Sender address, as a plain email string, an RFC 5322 mailbox string (Jane <jane@acme.com>), or an object with an optional display name. Must be from a verified domain in this workspace.

Show child parameters
to
array of string or object
required

Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (Jane <jane@acme.com>), or an object with an optional display name.

Show child parameters
cc
array of string or object

CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (Jane <jane@acme.com>), or an object with an optional display name.

Show child parameters
bcc
array of string or object

BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (Jane <jane@acme.com>), or an object with an optional display name.

Show child parameters
subject
string

Message subject line. Required for inline sends. Omit it when sending a template (the template supplies the subject).

html
string

HTML body. At least one of html or text must be provided.

text
string

Plain-text body. At least one of html or text must be provided.

reply_to
array of string or object

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.

Show child parameters
headers
object

Custom email headers, such as References, In-Reply-To, and X-* headers. Reserved headers and values containing carriage returns or line feeds return 422. Set addressing and subject through their dedicated fields. Transactional sends accept List-Unsubscribe and List-Unsubscribe-Post. Marketing sends use Bird-managed unsubscribe by default.

tags
array of object

Structured {name, value} labels, up to 20 per send. You can filter the list endpoint by tag name, slice analytics by tag, and read them in webhook payloads. Use them for low-cardinality dimensions such as category, experiment_variant or template_id, and metadata for context you do not filter by.

Show child parameters
metadata
object

JSON object returned on reads and in webhook payloads, up to 2 KB serialized. Analytics can query its paths, such as metadata.order_id, but the dashboard cannot filter by it. Use it for per-send context such as order IDs and customer references, and tags for low-cardinality labels you filter by.

reference
string

An identifier you want to find this message by, such as an order or ticket number, matched exactly by the message list's reference filter. Set it only when you need to search on it, and put any other data in metadata. Stored on the message, returned on reads and echoed in webhook payloads. Several messages can share one.

parameters
object

Values for the Liquid tokens in inline content, shared by every recipient: {{ animal }} takes the animal value, and a missing value renders empty. Include the object, even as {}, to render Liquid, or omit it to leave tokens unchanged. Names are single words other than bird, and the object is capped at 16 KB serialized. A stored template takes template.parameters instead. Inline personalization covers validation and URL encoding.

template
object

Send a stored template instead of inline content. When set, omit subject, html and text, because the template supplies them. Personalize with template.parameters. Add scheduled_at to send it later.

Show child parameters
track_opens
boolean

Whether to track open events for this message.

track_clicks
boolean

Whether to track click events for this message.

ip_pool_id
string

ID of the IP pool to send from (ipp_ prefix), or ipp_shared to route through the shared pool explicitly. Omit to use the sending domain's default pool in this workspace, or the organization's default when the domain has none. An unknown pool, or a pool with no dedicated IPs available to send from, is rejected with a 422.

category
string

Content classification, which sets the suppression policy:

  • marketing: blocked by every suppression reason. A send with no template and no category defaults to it.
  • transactional: delivered through complaint and unsubscribe suppressions, for receipts, password resets and similar operational mail.

A template send that omits it takes the template's classification. Set it to classify one send differently from its template; it always takes precedence.

Possible values: marketing, transactional

attachments
array of object

Files to attach, up to 20 per message. A generated message is capped at 20 MB, and a send over it is refused. The cap counts the HTML and text bodies and every attachment and inline image after base64 encoding, which adds about a third, so 15 MB of raw files uses most of it. Each batch item has the same cap, and the whole request body is capped at 20 MB too.

Show child parameters
scheduled_at
string

Send at this future time instead of immediately, at least 30 seconds and at most 30 days ahead; outside that range returns 422. The message returns accepted and reads as scheduled until it sends, and the message cancel endpoint cancels it before then. Scheduled sends count against your plan's monthly scheduled-email allowance, and a send over it returns 422. A stored template's published version, language and parameter values are pinned at acceptance, so a later publication does not change what sends; deleting the template before the message is due rejects it with generation_failure. Sender eligibility and send-volume allowance are checked again when it is due. Batch items take this field too, so one batch can mix scheduled and immediate messages.

Response Payload

id
string
required

Message ID.

from
object
required

Sender address. name is present when a display name was provided on the send.

Show child attributes
to
array of object
required

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.

Show child attributes
cc
array of object

CC recipients.

Show child attributes
bcc
array of object

BCC recipients.

Show child attributes
subject
string
required

The resolved subject. For a template send with recipient snapshots, this is the subject accepted for the first To recipient in the original request. It remains available when body storage is disabled. Legacy messages use their message-wide subject and caller parameters. For sends with recipient snapshots, subject_recipient_id identifies the recipient represented by subject and is present for scheduled and canceled messages too.

subject_recipient_id
string

Recipient represented by subject. Present for template sends with recipient snapshots, including scheduled and canceled messages.

category
string
required

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.

Possible values: marketing, transactional

reply_to
nullable array

Reply-To addresses, if set on the send. Empty/null when no Reply-To was provided.

status
string
required

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.

Possible values: scheduled, accepted, processed, deferred, delivered, partial_failure, bounced, complained, rejected, canceled

accepted_count
integer
required

How many recipients are in the accepted state, meaning we have the message and are getting ready to deliver it.

processed_count
integer
required

How many recipients the message has been prepared for and queued for delivery.

delivered_count
integer
required

How many recipients' messages were accepted by their mail server.

bounced_count
integer
required

Number of recipients that resulted in a permanent delivery failure.

complained_count
integer
required

Number of recipients that reported spam.

deferred_count
integer
required

Number of recipients in transient delivery deferral. Their mail server asked for a retry, and delivery attempts continue.

rejected_count
integer
required

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
nullable integer

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
nullable integer

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
nullable integer

End-to-end accept → delivered time for the fastest delivered recipient, in milliseconds. Null until the first recipient is delivered.

open_count
integer
required

Total open events across all recipients.

click_count
integer
required

Total click events across all recipients.

requested_language
nullable string

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
nullable string

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
nullable string

The template this send rendered from, or null for a send that supplied its content inline.

template_version_id
nullable string

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
string

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
array of object

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.

Show child attributes
metadata
object

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.

reference
string

The reference from the send request. Absent when the send set none.

parameters
nullable object

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
array of object

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.

Show child attributes
track_opens
boolean
required

Whether open tracking is enabled for this send.

track_clicks
boolean
required

Whether click tracking is enabled for this send.

created_at
string
required

When the send request was accepted.

thread_id
nullable string

Thread this message belongs to, or null when the message is not part of one.

in_reply_to_message_id
nullable string

The message this one is a reply to, if any.

delivered_at
nullable string

When all recipients reached a terminal delivered state, or null if not yet fully delivered.

scheduled_at
string

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.

Continue with the documentation, guides and examples for this topic.