---
title: Templates
description: Pre-approved message content with declared variables. Optional for email; required for SMS and WhatsApp.
---

A **template** is pre-approved message content with declared
variables. You reference it by id (`tpl_…`) when you send; we
render the body against the variables you pass.

Templates aren't required for every channel:

| Channel  | Templates  | Why                                                                    |
| -------- | ---------- | ---------------------------------------------------------------------- |
| Email    | Optional   | You can also send a raw HTML or text body directly.                    |
| SMS      | Required   | Carrier networks require pre-registered content for transactional traffic. |
| WhatsApp | Required   | WhatsApp's network only delivers approved templates outside the 24h window. |

When you do use a template, you get three things that direct
sends can't offer:

- **Editable copy.** Your support team can tweak wording in the
  dashboard without a code deploy.
- **Server-injected brand.** Variables like `{{brand.name}}` and
  `{{brand.support_email}}` are filled in from your verified
  domain at send time. A leaked API key can't impersonate
  another brand on your team.
- **Variable validation.** Required keys, type checks, and
  missing-variable errors run before the message is dispatched.

## What's in a template

Each template has:

- A **name** for humans (e.g. "Order confirmation").
- A **channel** — `email`, `sms`, or `whatsapp`.
- A **content** body with `{{handlebars}}` placeholders.
- A **variable manifest** — the declared list of variables the
  template accepts, with type and required/optional metadata.

When you call `POST /v1/{channel}`, we validate the variables
you pass against the manifest and reject mismatches before the
send leaves the API.

## Authoring templates

Templates are authored in the dashboard under **Templates → New
template**. Pick the channel, write the body, and declare
variables as you go. The dashboard previews the rendered output
side-by-side using sample values.

For email, the body is HTML with a plain-text fallback. For SMS,
the body is plain text capped at 1600 characters once rendered.
For WhatsApp, the body matches a pre-registered template body
on the WhatsApp network — only the variable values you pass
travel over the wire; the body itself is rendered by the
provider from the pre-registered version.

## Variable manifest

Each declared variable has:

| Field        | Meaning                                                                           |
| ------------ | --------------------------------------------------------------------------------- |
| `key`        | The placeholder name, used as `{{customer.key}}`.                                 |
| `label`      | A human label shown in the dashboard preview.                                     |
| `required`   | When `true`, omitting it returns `422 missing_variable`.                          |
| `type`       | One of `string`, `number`, `email`, `phone`, `url`, `code`.                       |

The order of variables matters for **WhatsApp**: it's the order
of the positional placeholders (`{{1}}`, `{{2}}`, …) in the
pre-registered template body. The dashboard makes the ordering
visible so you can't get it wrong.

## Send-time substitution

Variables you send live in the `customer.*` namespace and the
brand fields live in `brand.*`. So a template body like:

```text
Hi {{customer.first_name}}, your {{brand.name}} verification
code is {{customer.code}}. Reply STOP to opt out.
For help, email {{brand.support_email}}.
```

With this request:

```json
{
  "to": "+2348012345678",
  "template_id": "tpl_…",
  "variables": {
    "first_name": "Ada",
    "code": "847291"
  }
}
```

Renders as:

```text
Hi Ada, your Acme verification code is 847291.
Reply STOP to opt out. For help, email support@acme.com.
```

You never pass `brand.*` values from your code — they're filled
in from your domain's brand profile on the server side.

## Re-using templates across teams

Templates are scoped to your team. If you have a staging team
and a production team, author the templates in each one (you
can copy-paste). Keeping them separate means a draft template
can't accidentally go live before you mean it to.