Templates

Templates let you author a message once and run it across email, SMS, WhatsApp, and voice. Variable substitution is the same shape on every channel; what differs is the rendering target — React on email, plain text on SMS, Meta-approved bodies on WhatsApp, TTS on voice.

Email templates

React Email is the recommended authoring path. MJML and raw HTML are both accepted as alternatives — pass mjml: or html: instead of react:. The SDK renders React Email components to MIME-safe HTML + a plain-text fallback before the request leaves your process.

// emails/welcome.tsx
import { Html, Body, Container, Heading, Text, Button } from "@react-email/components";

export function WelcomeEmail({ name }: { name: string }) {
  return (
    <Html>
      <Body>
        <Container>
          <Heading>Hi {name}, your invite is ready.</Heading>
          <Text>You can start sending in under five minutes.</Text>
          <Button href="https://bird.dev/docs/quickstart">Read the quickstart</Button>
        </Container>
      </Body>
    </Html>
  );
}
import { WelcomeEmail } from "./emails/welcome"

const { data, error } = await bird.email.send({
  to:      ["delivered@bird.dev"],
  from:    "Bird <hello@bird.dev>",
  subject: "Your Bird invite is ready",
  react:   <WelcomeEmail name="Ada" />,
})

SMS templates

SMS templates are stored bodies with {{variable}} substitution. The SDK counts segments and warns when a render would push you over a segment boundary; Unicode runs are detected automatically and tracked as 70-char segments instead of 160.

Define the template once, send it by name:

const { data, error } = await bird.sms.send({
  to: "+15005550006",
  from: "+18885550101",
  template: "otp_default",
  variables: { code: "482917" },
});

The template body lives in your account — Hi {{name}}, your code is {{code}}. Don't share it. — and is referenced by name on every send. The response includes segments and encoding so you can log per-recipient cost.

WhatsApp templates

WhatsApp templates require Meta approval before they can be sent outside a 24-hour session window. The flow is: submit, Meta reviews, approved (or rejected), webhook fires, you can send.

Gotchas to know up front:

  • Header media type is fixed at approval time. A template approved with an image header cannot later send with a video header — submit a separate template.
  • Buttons have hard limits. Three reply buttons or two CTAs max; mixing types in one template is rejected.
  • Variable index ordering is positional. Meta sees {{1}}, {{2}}, {{3}} and your SDK call must pass variables in the same order they appear in the body.
  • Locale variants are separate templates upstream. Bird groups them under one name on send; Meta tracks each en_US, es_MX, pt_BR as its own approved row.
const { data: tmpl, error: createError } = await bird.whatsapp.templates.create({
  name: "order_confirmed",
  language: "en_US",
  category: "UTILITY",
  body: "Hi {{1}}, your order {{2}} is on its way. ETA: {{3}}",
  variables: ["name", "order_id", "eta"],
});

// `tmpl.state` is "pending" — wait for the `template.approved` webhook

const { data: msg, error: sendError } = await bird.whatsapp.send({
  to: "+15005550009",
  from: "+18885550101",
  template: "order_confirmed",
  variables: { name: "Ada", order_id: "A-2491", eta: "today, 4pm" },
});

Voice templates

Voice templates are TTS bodies with the same {{variable}} syntax as SMS. Use them in a say step inside a flow.

const { data, error } = await bird.voice.calls.create({
  to: "+15005550010",
  from: "+18885550101",
  flow: [
    {
      say: {
        template: "verification_voice",
        variables: { code: "482917" },
        voice: "en-US-amy",
      },
    },
  ],
});

The stored template body — Your verification code is {{code}}. Again, {{code}}. — is rendered to audio by Bird's streaming TTS at call time. First audio byte under 250ms.

The bird.templates.* SDK surface

Channel-agnostic stored templates live under bird.templates. WhatsApp's Meta-approved templates live under bird.whatsapp.templates and are covered above.

MethodEndpointWhat it does
bird.templates.createPOST /v1/templatesDefine a new template with channel-specific bodies.
bird.templates.getGET /v1/templates/{name}Fetch the latest version (or a pinned version) of a template.
bird.templates.listGET /v1/templatesList templates, filterable by channel and tag.
bird.templates.updatePATCH /v1/templates/{name}Create a new version. Prior versions remain renderable.

get supports If-None-Match against the version etag. update is additive — past versions remain pinnable from sends with template: { name, version: 3 }.

The template-sync CLI

@bird/templates is an OSS library — and CLI — for authoring templates in code and syncing them to your Bird account. Source at github.com/bird-io/templates. One command in CI keeps your stored templates in lockstep with your repo:

bird templates sync ./templates

The CLI also runs a local preview server for iterating on React Email templates without round-tripping to the API.

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.

Get startedRead docsor

Using Claude Code, Cursor, or Codex? Point it at our MCP server — 141 tools, one per API endpoint, with scoped agent keys.