Create an email message
/v1/email/messagesconst 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"msg = client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
html="<p>My first Bird email.</p>",
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Email.Send(context.Background(), bird.EmailSendParams{
From: "onboarding@messagebird.dev",
To: []string{"delivered@messagebird.dev"},
Subject: "Hello from Bird",
HTML: "<p>My first Bird email.</p>",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$message = $bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
subject: 'Hello from Bird',
html: '<p>My first Bird email.</p>',
);
echo $message->getId(), ' ', $message->getStatus();bird email send \
--category transactional \
--cc manager@acme.com \
--from 'Acme Support <noreply@acme.com>' \
--header X-Campaign=spring-2026 \
--html '<h1>Hi there 👋</h1>' \
--metadata '{"user_id":"usr_12345"}' \
--reply-to support@acme.com \
--subject 'Welcome aboard' \
--tag category=welcome \
--text 'Hi there' \
--to 'Jane Doe <delivered@messagebird.dev>' \
--track-clicks=falsecurl -X POST "https://us1.platform.bird.com/v1/email/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"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": "<h1>Hi there 👋</h1>",
"text": "Hi there",
"headers": {
"X-Campaign": "spring-2026"
},
"tags": [
{
"name": "category",
"value": "welcome"
}
],
"metadata": {
"user_id": "usr_12345"
},
"category": "transactional",
"track_clicks": false
}'{
"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.
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
fromSender 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
Email address, optionally in RFC 5322 mailbox form with an embedded display name.
An email address with an optional display name.
from.emailEmail address.
from.nameDisplay name shown alongside the address in mail clients.
toPrimary 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
Email address, optionally in RFC 5322 mailbox form with an embedded display name.
An email address with an optional display name.
to.emailEmail address.
to.nameDisplay name shown alongside the address in mail clients.
ccCC 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
Email address, optionally in RFC 5322 mailbox form with an embedded display name.
An email address with an optional display name.
cc.emailEmail address.
cc.nameDisplay name shown alongside the address in mail clients.
bccBCC 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
Email address, optionally in RFC 5322 mailbox form with an embedded display name.
An email address with an optional display name.
bcc.emailEmail address.
bcc.nameDisplay name shown alongside the address in mail clients.
subjectMessage subject line. Required for inline sends. Omit it when sending a template (the template supplies the subject).
htmlHTML body. At least one of html or text must be provided.
textPlain-text body. At least one of html or text must be provided.
reply_toReply-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
Email address, optionally in RFC 5322 mailbox form with an embedded display name.
An email address with an optional display name.
reply_to.emailEmail address.
reply_to.nameDisplay name shown alongside the address in mail clients.
headersCustom 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.
tagsStructured {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.
Show child parameters
tags.nameTag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
tags.valueTag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
metadataArbitrary 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.
parametersParameter 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 for validation and URL encoding examples.
templateSend 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
template.idThe template to send, by its id.
template.languageWhich 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.
template.parametersValues for caller parameters, keyed by name. A parameter name is a single word.
Caller parameters have system set to false or absent in the template's
variables list. Supply each required caller parameter used by the
resolved send language; omitting one returns 422. A version's list
covers all its languages, and values unused by the resolved language are
ignored.
The bird namespace is reserved for values filled by Bird, so a send that
sets it is rejected. parameters is capped at 16 KB once serialized.
template.slugThe template to send, by its slug handle. A workspace template (for example welcome-email) or a built-in system template (for example bird_welcome).
template.languageWhich 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.
template.parametersValues for caller parameters, keyed by name. A parameter name is a single word.
Caller parameters have system set to false or absent in the template's
variables list. Supply each required caller parameter used by the
resolved send language; omitting one returns 422. A version's list
covers all its languages, and values unused by the resolved language are
ignored.
The bird namespace is reserved for values filled by Bird, so a send that
sets it is rejected. parameters is capped at 16 KB once serialized.
track_opensWhether to track open events for this message.
track_clicksWhether to track click events for this message.
ip_pool_idID 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.
categoryContent 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.
Possible values: marketing, transactional
attachmentsFiles 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.
Show child parameters
attachments.filenameThe name the recipient sees on the attachment.
attachments.contentBase64-encoded file bytes. The encoded value and MIME wrapping count toward the 20 MB message limit.
attachments.content_typeThe 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.
attachments.content_idAn RFC 2392 Content-ID for an inline file. Reference it from the HTML body with <img src="cid:{content_id}"/>. Omit it to send a downloadable attachment.
scheduled_atSchedule 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. For a stored template, the published version, language and parameter values are pinned when we accept the request, so a later publication does not change what sends. If the template is deleted before the message is due, the message is rejected with generation_failure. We also check sender eligibility and send-volume allowance when the message is due. Batch items take this field too, so one batch can mix scheduled and immediate messages.
Response Payload
idMessage ID.
fromSender address. name is present when a display name was provided on the send.
Show child attributes
from.emailEmail address.
from.nameDisplay name shown alongside the address in mail clients.
toPrimary 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
to.emailEmail address.
to.nameDisplay name shown alongside the address in mail clients.
ccCC recipients.
Show child attributes
cc.emailEmail address.
cc.nameDisplay name shown alongside the address in mail clients.
bccBCC recipients.
Show child attributes
bcc.emailEmail address.
bcc.nameDisplay name shown alongside the address in mail clients.
subjectThe 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.
categoryContent 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_toReply-To addresses, if set on the send. Empty/null when no Reply-To was provided.
statusaccepted_countHow many recipients are in the accepted state, meaning we have the message and are getting ready to deliver it.
processed_countHow many recipients the message has been prepared for and queued for delivery.
delivered_countHow many recipients' messages were accepted by their mail server.
bounced_countNumber of recipients that resulted in a permanent delivery failure.
complained_countNumber of recipients that reported spam.
deferred_countNumber of recipients in transient delivery deferral. Their mail server asked for a retry, and delivery attempts continue.
rejected_countNumber 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_msTime 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_msTime 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_msEnd-to-end accept → delivered time for the fastest delivered recipient, in milliseconds. Null until the first recipient is delivered.
open_countTotal open events across all recipients.
click_countTotal click events across all recipients.
requested_languageThe 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_languageThe 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_idThe template this send rendered from, or null for a send that supplied its content inline.
template_version_idThe 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_idThe 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.
tagsLabels 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
tags.nameTag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
tags.valueTag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
metadataAny 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.
parametersThe 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.
attachmentsAttachment 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
attachments.idAttachment ID, stable per email send.
attachments.filenameFilename as shown to the recipient.
attachments.content_typeResolved MIME type at send time.
attachments.sizeDecoded size in bytes.
attachments.inlineTrue when the attachment was sent inline via a content_id reference in the HTML body, false for regular file attachments.
attachments.content_idThe Content-ID set at send time, when the attachment was inline.
track_opensWhether open tracking is enabled for this send.
track_clicksWhether click tracking is enabled for this send.
created_atWhen the send request was accepted.
thread_idThread this message belongs to, or null when the message is not part of one.
in_reply_to_message_idThe message this one is a reply to, if any.
delivered_atWhen all recipients reached a terminal delivered state, or null if not yet fully delivered.
scheduled_atWhen 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.
Related resources
Continue with the documentation, guides and examples for this topic.