Send SMS

One API for every text you send.

Send transactional messages and notifications through Bird. Supply your text and sender, or use a template; inspect the encoding and segment count in the response. Add an idempotency key for safe retries and follow delivery through signed webhooks.

Cursor

One message. A visible outcome.

Example send

FFieldnotes
Your order #4821 is ready to collect.
Status202 Accepted
EncodingGSM-7
Segments1

Explore acceptance and a later carrier receipt. This example does not send a text; delivery does not establish that someone read it.

Trusted every day by teams that build world-class software

Read more customer stories

Test your first SMS integration.

From the language you already use.

Sending is the core of the Bird SMS API. The example below shows the request shape. For a controlled test, replace the recipient with the documented sandbox number +15005550006. Set up a suitable US sender and enable the destination first, then verify acceptance and delivery events before sending to customers.

1
2
3
4
5
6
7
const msg = await bird.sms.send({
  from: "+15557654321",
  to: "+14155550100",
  text: "Your verification code is 123456.",
  category: "authentication",
});
console.log(msg.id, msg.status);

An SMS send delivers the text you supply. For login and account verification, use Bird Verify to generate, expire and check codes as part of a verification flow.

Build on a clear sending contract.

Prepare the request and follow the result.

  1. 01

    Segment counting before send.

    Bird reports the calculated encoding and segment count in its response. Use the segment calculator to inspect a draft before you submit it.

  2. 02

    GSM-7 and Unicode, decided for you.

    Characters determine the encoding. GSM-7 fits 160 units in a single segment; Unicode fits 70. Multipart messages reserve space for reassembly, and emoji can occupy more than one unit.

  3. 03

    Batch in one call.

    Submit up to 100 independent messages in one batch. Validation happens before enqueueing; each accepted message then has its own outcome.

  4. 04

    Retry with an idempotency key.

    Use one idempotency key per logical request and reuse it for an identical retry. The retained API response can be replayed; this does not guarantee exactly-once carrier delivery.

  5. 05

    Delivery events for your application.

    Subscribe to accepted, sent and terminal outcome events. Verify signatures, deduplicate webhook retries and use message reads to investigate missing or delayed observations.

Move the integration with a controlled test.

Map your current request fields, sender registrations and event handling to Bird. Reconcile opt-outs before moving traffic, then compare a controlled test before changing production routing.

twilio.ts
Twilio
import twilio from "twilio";

const client = twilio(accountSid, authToken);

await client.messages.create({
  from: "+14155550172",
  to:   "+15005550006",
  body: "Your code is 123456.",
});
bird.ts
Bird
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

await bird.sms.send({
  from:     "+14155550172",
  to:       "+15005550006",
  text:     "Your code is 123456.",
  category: "authentication",
});

Know the segment count before submission.

GSM-7 fits 160 septets in a single segment; UCS-2 fits 70 code units. Multipart capacity is 153 or 67 respectively. Extended GSM-7 characters use two septets and emoji may use two code units. Bird returns encoding and segments at acceptance; the applicable rate and any carrier fee are priced separately.

segments.ts
202 · 1 segment
const { data, error } = await bird.sms.send({
  from:     "Bird",
  to:       "+31612345678",
  text:     "Your code is 123456.",
  category: "authentication",
}).safe();
if (error) throw error;

console.log(data.segments);
// → { characters: 20, count: 1, encoding: "GSM_7BIT" }

One message or a hundred, one call.

Batch up to 100 independent messages, each with its own recipient and text. Invalid input rejects the request before enqueueing. After a successful 202 response, processing and delivery can succeed or fail separately for each SMS. Reuse the request and idempotency key when retrying within the documented retention window.

reminders.ts
202 · batch
const { data: batch, error } = await bird.sms
  .sendBatch(
    users.map((u) => ({
      from: "Bird",
      to:   u.phone,
      text: `Hi ${u.name}, your appointment is tomorrow at ${u.time}.`,
    })),
    { idempotencyKey: `reminders-${runId}` },
  )
  .safe();

if (error) throw error;
console.log(`queued ${batch.data.length} messages`);

Follow acceptance through to the reported outcome.

A successful request returns 202 Accepted. Charging and carrier submission happen later and can still fail. Consume signed delivery events and inspect the message record when investigating the outcome.

app/api/webhooks/bird/route.ts
signed
import { bird } from "@/lib/bird";

export async function POST(req: Request) {
  const event = bird.webhooks.unwrap(
    await req.text(),
    Object.fromEntries(req.headers),
  );

  switch (event.type) {
    case "sms.delivered":
      await markDelivered(event.data.sms_id);
      break;
    case "sms.failed":
      await flag(event.data.to, event.data.error?.description);
      break;
  }

  return new Response(null, { status: 204 });
}

Inspect failures by their reported reason. Supported STOP keywords and carrier opt-outs create suppressions; other delivery failures do not automatically become an opt-out.

  • sms.acceptedAccepted by the API and queued for the carrier hand-off.
  • sms.sentSubmitted to the destination carrier's SMSC.
  • sms.deliveredDelivery receipt received from the carrier (DLR).
  • sms.failedA terminal failure for this SMS attempt. Inspect the reported error and the message timeline.

Go deeper in the docs.

Wire up webhooks, make every send safe to retry with idempotency keys, and read the error reference so you handle each failure the right way.

Questions before you build

Do I choose the sender?
For a free-text send, provide a sender your workspace can use in the destination and the appropriate message category. A system-template send resolves its category and sender from the template.
How do retries avoid a duplicate text?
Provide an idempotency key and reuse it for a retry of the same request. A send without that key can be treated as a new message.
Does accepted mean delivered?
No. A 202 response means the API accepted the request. Follow the message record and signed events for the carrier-reported outcome. A delivery receipt does not establish that the recipient read the text.
Is a batch the same as a broadcast?
A batch contains up to 100 independent messages, each with its own recipient and body. A broadcast is an audience campaign with shared content and a managed send lifecycle. Choose the workflow that matches your job.

Build the complete messaging workflow.

Connect SMS sending to the sender, destination and delivery controls your application needs. Prepare the integration before sending to customers.

Your details

All contact fields are required.

So our team can reach you about your demo.

Products of interest

Optional

We’ll contact you to arrange your demo.
Privacy policy

Start with one channel.
Add the others when you're ready.

A test API key is yours immediately. Production unlocks when you add a payment method and verify a sender.

Using Claude Code, Cursor, or Codex? Copy a setup prompt and your agent installs the Bird CLI and skills for you. Pick yours:

Cursor