Create a batch of SMS messages
/v1/sms/batchesconst result = await bird.sms.sendBatch({
messages: [
{
from: "+15557654321",
to: "+15551111111",
text: "Hi Alice!",
category: "marketing",
},
{
from: "+15557654321",
to: "+15552222222",
text: "Hi Bob!",
category: "marketing",
},
],
});batch = client.sms.send_batch(
messages=[
{
"from_": "+15557654321",
"to": "+15551111111",
"text": "Hi Alice!",
"category": "marketing",
},
{
"from_": "+15557654321",
"to": "+15552222222",
"text": "Hi Bob!",
"category": "marketing",
},
]
)
for msg in batch.data:
print(msg.id, msg.status)batch, err := client.Sms.SendBatch(context.Background(), bird.SmsSendBatchParams{
Messages: []bird.SmsSendParams{
{
From: "+15557654321", To: "+15551111111",
Text: "Hi Alice!", Category: bird.SMSCategoryMarketing,
},
{
From: "+15557654321", To: "+15552222222",
Text: "Hi Bob!", Category: bird.SMSCategoryMarketing,
},
},
})
if err != nil {
log.Fatal(err)
}
for _, msg := range batch.Data {
fmt.Println(msg.Id, *msg.Status)
}$batch = $bird->sms->sendBatch(messages: [
(new SMSMessageSendRequest())
->setFrom('+15557654321')
->setTo('+15551111111')
->setText('Hi Alice!')
->setCategory('marketing'),
(new SMSMessageSendRequest())
->setFrom('+15557654321')
->setTo('+15552222222')
->setText('Hi Bob!')
->setCategory('marketing'),
]);
foreach ($batch->getData() ?? [] as $item) {
echo $item->getId(), ' ', $item->getStatus(), "\n";
}curl -X POST "https://us1.platform.bird.com/v1/sms/batches" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+15551111111",
"from": "+15557654321",
"text": "Hi Alice!",
"category": "marketing"
},
{
"to": "+15552222222",
"from": "+15557654321",
"text": "Hi Bob!",
"category": "marketing"
}
]
}'{
"data": [
{
"id": "sms_01krdgeqcxet5s7t44vh8rt9mg",
"direction": "outbound",
"status": "scheduled",
"to": "+15551234567",
"from": "+15557654321",
"text": "Your order has shipped and is on its way.",
"category": "transactional",
"requested_language": "pt-BR",
"resolved_language": "pt-BR",
"template_id": "smt_01krdgeqcxet5s7t44vh8rt9mg",
"template_version_id": "smv_01krdgeqcxet5s7t44vh8rt9mg",
"template_content_hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"segments": {
"count": 1,
"encoding": "GSM_7BIT",
"characters": 41
},
"cost": {
"amount": "0.00990",
"currency_code": "USD",
"transaction_amount": "0.00790",
"passthrough_amount": "0.00200"
},
"tags": [
{
"name": "category",
"value": "welcome"
}
],
"options": {
"smart_encoding": true
},
"carrier": "Verizon",
"mcc_mnc": "311480",
"last_error": {
"code": "invalid_destination",
"description": "Carrier filtered as spam"
},
"created_at": "2026-05-21T12:00:00Z"
}
],
"summary": {
"accepted_count": 2
}
}
Sends up to 100 independent SMS messages in one request. Each item is a complete send request with its own recipient, content, ID, status, and cost. For a single message, use Send an SMS message instead.
Acceptance is all-or-nothing: every item is validated before any is queued,
and one invalid item rejects the whole batch with a 422 (nothing is
sent). A batch from a workspace with no wallet balance fails with a 402.
The 202 response lists the accepted messages in submission order; each
delivers asynchronously and is tracked individually, like a single send.
Request Payload
messagesSMS message send requests, up to 100. Each is an independent send; all are validated before any is queued.
Show child parameters
messages.toRecipient phone number in E.164 format (for example +14155550100). One recipient per message. The number is stored and returned in canonical E.164; a recipient that cannot be routed returns a 422 SMSInvalidRecipient.
messages.fromSender to send from. It must be a sender the workspace holds: a number it owns in E.164, such as +15557654321, a short code it holds, such as 24680, or an alphanumeric sender ID it has claimed, such as MyBrand. A sender the workspace does not hold returns a 422 SMSSenderNotConfigured, and an alphanumeric sender must also be permitted, and where required registered, for the destination country. Required on a free-text send and when sending a workspace template. Omitting it in either case returns 422. A built-in template selects its sender automatically and rejects from.
messages.textFree-text message body. Required unless template is supplied (the two are mutually exclusive). At least 1 character, up to a 12-segment cap (roughly 1836 GSM-7 or 804 UCS-2 characters). Bird does not truncate; a body exceeding 12 segments is rejected with a 422. The cap applies to segments because GSM-7 and UCS-2 encodings differ in characters per segment.
messages.categoryContent classification: why you are sending. Required on a free-text send; omit it on a template send, where the category is derived from the template. Where the destination country requires the sender to be registered, a category outside what that registration covers returns a 422 SenderCategoryNotPermitted.
Possible values: transactional, marketing, authentication, service
messages.validity_periodPreview feature: how long, in seconds (60-172800), the carrier may keep attempting delivery before the message is marked expired. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.tagsStructured {name, value} labels for filtering and analytics. Tags become first-class query dimensions: filter the list endpoint by tag name, slice analytics by tag, and surface in webhook payloads. Maximum 20 tags per send. Use tags for low-cardinality dimensions (category, experiment_variant). For arbitrary structured context you do not need as a filter dimension, use metadata instead.
messages.metadataArbitrary JSON object stored on the message, returned on API reads, and echoed in webhook payloads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use tags instead.
messages.optionsWhat Bird does to this message on its way out, such as smart_encoding. The message being relayed stays at the top level: its recipient, sender, content, and the delivery instructions the carrier acts on.
messages.media_urlsPreview feature: multimedia (MMS) attachments. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.messaging_profile_idPreview feature: sender selection from a messaging profile pool. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.scheduled_atPreview feature: send-later scheduling. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.broadcast_idPreview feature: broadcast correlation. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.campaign_idPreview feature: campaign correlation for analytics. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.audience_idPreview feature: audience-targeted sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.contact_idPreview feature: contact-targeted sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.topic_idPreview feature: topic-gated sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.personalizationPreview feature: per-recipient substitution for batch sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.toRecipient phone number in E.164 format (for example +14155550100). One recipient per message. The number is stored and returned in canonical E.164; a recipient that cannot be routed returns a 422 SMSInvalidRecipient.
messages.fromSender to send from. It must be a sender the workspace holds: a number it owns in E.164, such as +15557654321, a short code it holds, such as 24680, or an alphanumeric sender ID it has claimed, such as MyBrand. A sender the workspace does not hold returns a 422 SMSSenderNotConfigured, and an alphanumeric sender must also be permitted, and where required registered, for the destination country. Required on a free-text send and when sending a workspace template. Omitting it in either case returns 422. A built-in template selects its sender automatically and rejects from.
messages.categoryContent classification: why you are sending. Required on a free-text send; omit it on a template send, where the category is derived from the template. Where the destination country requires the sender to be registered, a category outside what that registration covers returns a 422 SenderCategoryNotPermitted.
Possible values: transactional, marketing, authentication, service
messages.validity_periodPreview feature: how long, in seconds (60-172800), the carrier may keep attempting delivery before the message is marked expired. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.tagsStructured {name, value} labels for filtering and analytics. Tags become first-class query dimensions: filter the list endpoint by tag name, slice analytics by tag, and surface in webhook payloads. Maximum 20 tags per send. Use tags for low-cardinality dimensions (category, experiment_variant). For arbitrary structured context you do not need as a filter dimension, use metadata instead.
messages.metadataArbitrary JSON object stored on the message, returned on API reads, and echoed in webhook payloads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use tags instead.
messages.optionsWhat Bird does to this message on its way out, such as smart_encoding. The message being relayed stays at the top level: its recipient, sender, content, and the delivery instructions the carrier acts on.
messages.media_urlsPreview feature: multimedia (MMS) attachments. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.messaging_profile_idPreview feature: sender selection from a messaging profile pool. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.scheduled_atPreview feature: send-later scheduling. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.templateSend using a stored template instead of free text. The category is derived from the template, so category and media_urls are rejected. A workspace template requires from; a built-in template selects its sender and rejects from.
Show child parameters
messages.template.idThe workspace or built-in template to send, by ID.
messages.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 carry 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.
messages.template.parametersValues for the template's variables, keyed by variable name. Read the live version to see the accepted keys and formats. A missing key, an undeclared key, an invalid value, or a serialized object over 16 KiB returns 422.
messages.template.slugThe workspace or built-in template to send, by its immutable slug. Read the template's live version to see its variables.
messages.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 carry 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.
messages.template.parametersValues for the template's variables, keyed by variable name. Read the live version to see the accepted keys and formats. A missing key, an undeclared key, an invalid value, or a serialized object over 16 KiB returns 422.
messages.template.nameDeprecated. Use slug instead. This resolves legacy built-in catalogue names and never matches a workspace template's display name.
messages.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 carry 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.
messages.template.parametersValues for the template's variables, keyed by variable name. Read the live version to see the accepted keys and formats. A missing key, an undeclared key, an invalid value, or a serialized object over 16 KiB returns 422.
messages.broadcast_idPreview feature: broadcast correlation. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.campaign_idPreview feature: campaign correlation for analytics. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.audience_idPreview feature: audience-targeted sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.contact_idPreview feature: contact-targeted sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.topic_idPreview feature: topic-gated sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
messages.personalizationPreview feature: per-recipient substitution for batch sends. Currently unavailable; supplying this field returns 422 SMSUnsupportedFeature.
Response Payload
dataOne entry per message in the batch, in submission order.
Related resources
Continue with the documentation, guides and examples for this topic.