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 passvariablesin 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_BRas 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.
| Method | Endpoint | What it does |
|---|---|---|
bird.templates.create | POST /v1/templates | Define a new template with channel-specific bodies. |
bird.templates.get | GET /v1/templates/{name} | Fetch the latest version (or a pinned version) of a template. |
bird.templates.list | GET /v1/templates | List templates, filterable by channel and tag. |
bird.templates.update | PATCH /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.