Create a batch of email messages
POST
/v1/email/batches
const batch = await bird.email.sendBatch([
{
from: { email: "onboarding@messagebird.dev", name: "Bird" },
to: ["alice@example.com"],
subject: "Your receipt",
html: "<p>Thanks, Alice.</p>",
},
{
from: { email: "onboarding@messagebird.dev", name: "Bird" },
to: ["bob@example.com"],
subject: "Your receipt",
html: "<p>Thanks, Bob.</p>",
},
]);
for (const item of batch.data) console.log(item.id, item.status);batch = client.email.send_batch(
messages=[
{
"from_": {"email": "onboarding@messagebird.dev", "name": "Bird"},
"to": ["delivered@messagebird.dev"],
"subject": "Hello from Bird",
"html": "<p>My first Bird email.</p>",
},
{
"from_": {"email": "onboarding@messagebird.dev", "name": "Bird"},
"to": ["someone-else@messagebird.dev"],
"subject": "Hello again from Bird",
"text": "My second Bird email.",
},
],
)
for item in batch.data:
print(item.id, item.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)
}
batch, err := client.Email.SendBatch(context.Background(), bird.EmailSendBatchParams{
Messages: []bird.EmailSendParams{
{
From: "onboarding@messagebird.dev",
To: []string{"alice@example.com"},
Subject: "Hello, Alice",
HTML: "<p>Welcome!</p>",
},
{
From: "onboarding@messagebird.dev",
To: []string{"bob@example.com"},
Subject: "Hello, Bob",
HTML: "<p>Welcome!</p>",
},
},
})
if err != nil {
log.Fatal(err)
}
for _, item := range batch.Data {
fmt.Println(item.Id)
}
}$batch = $bird->email->sendBatch([
(new EmailMessageSendRequest())
->setFrom((new EmailAddress())->setEmail('onboarding@messagebird.dev')->setName('Bird'))
->setTo([(new EmailAddress())->setEmail('delivered@messagebird.dev')])
->setSubject('Hello from Bird')
->setHtml('<p>My first Bird email.</p>'),
(new EmailMessageSendRequest())
->setFrom((new EmailAddress())->setEmail('onboarding@messagebird.dev')->setName('Bird'))
->setTo([(new EmailAddress())->setEmail('someone-else@messagebird.dev')])
->setSubject('Hello again from Bird')
->setText('My second Bird email.'),
]);
foreach ($batch->getData() ?? [] as $item) {
echo $item->getId(), ' ', $item->getStatus(), "\n";
}bird email send-batch --body-file - <<'JSON'
[
{
"from": {
"email": "noreply@acme.com",
"name": "Acme Support"
},
"subject": "Your receipt for order #1234",
"text": "Thanks for your purchase! Your receipt is attached.",
"to": [
{
"email": "delivered@messagebird.dev",
"name": "Jane Doe"
}
]
},
{
"from": {
"email": "noreply@acme.com",
"name": "Acme Support"
},
"subject": "Your receipt for order #1235",
"text": "Thanks for your purchase! Your receipt is attached.",
"to": [
{
"email": "delivered@messagebird.dev",
"name": "John Roe"
}
]
}
]
JSONcurl -X POST "https://us1.platform.bird.com/v1/email/batches" \
-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"
}
],
"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."
}
]'Response202
{
"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"
}
]
}
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. None of the items can set scheduled_at; schedule a single message with Create an email message instead. 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.
Request Payload
Array of objects, each with:
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
from.email
string
required
Email address.
from.name
string
Display name shown alongside the address in mail clients.
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
to.email
string
required
Email address.
to.name
string
Display name shown alongside the address in mail clients.
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
cc.email
string
required
Email address.
cc.name
string
Display name shown alongside the address in mail clients.
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
bcc.email
string
required
Email address.
bcc.name
string
Display name shown alongside the address in mail clients.
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
reply_to.email
string
required
Email address.
reply_to.name
string
Display name shown alongside the address in mail clients.
headers
object
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
array of object
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.
Show child parameters
tags.name
string
required
Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
tags.value
string
required
Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
metadata
object
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.
parameters
object
Parameter values used to personalize inline content. A parameter is a single word, and a token in the subject or body (for example {{ animal }}) is replaced with the value of that name at send time. Shared across all recipients of this send. A token with no matching key renders empty. Cap: 16 KB serialized. When sending a stored template, put the values in template.parameters instead.
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. A template send goes out immediately: template and scheduled_at are mutually exclusive, and combining them is rejected with a 422.
Show child parameters
template.id
string
required
The template to send, by its id.
template.slug
string
required
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).
template.language
string
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.
template.parameters
object
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.
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 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
string
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.
Possible values: marketing, transactional
attachments
array of object
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.
Show child parameters
attachments.filename
string
required
The name the recipient sees on the attachment.
attachments.content
string
required
Base64-encoded file bytes. The encoded value and MIME wrapping count toward the 20 MB message limit.
attachments.content_type
string
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.
attachments.content_id
string
An 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_at
string
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. This field is accepted only on a single send. Batch items reject it.
Response Payload
data
array of object
required
One entry per message in the batch, in submission order.
Show child attributes
data.id
string
required
Message ID assigned to this batch item.
data.status
string
required
Initial status of this message in the batch.
Possible values: accepted
data.category
string
required
Resolved category for this batch item.
Possible values: marketing, transactional
data.requested_language
nullable string
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.
data.resolved_language
nullable string
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.
data.template_id
nullable string
The template this item rendered from, or null for an item that supplied its content inline.
data.template_version_id
nullable string
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.