---
title: Variables
description: How the customer and brand variable namespaces work.
---

Templates use [Handlebars](https://handlebarsjs.com)-style
placeholders. Each placeholder belongs to one of two namespaces:
`customer.*` (values from your API request) and `brand.*` (values
filled in by us from the verified domain you send from).

## `customer.*` — values from your API call

Anything you pass in `variables` lives under `customer.*`. The
template references them as `{{customer.<key>}}`. The variable
manifest on the template controls which keys are required and
what types they accept.

```json
{
  "to": "ada@example.com",
  "template_id": "tpl_…",
  "variables": {
    "first_name": "Ada",
    "amount": "₦12,500"
  }
}
```

Inside the template:

```text
Hi {{customer.first_name}}, your transfer of
{{customer.amount}} was successful.
```

If you pass a key the template doesn't declare, the API returns
`422 unknown_variable`. If you skip a required key, it returns
`422 missing_variable`. Both errors name the offending key in
`extensions.param` so your client can highlight the field.

## `brand.*` — values from your verified domain

The `brand` namespace is **never** passed in the API request.
We fill it in on the server side using the brand profile of the
verified domain the send is using. The keys are fixed:

| Key                   | Source                                 |
| --------------------- | -------------------------------------- |
| `brand.name`          | Brand name on the domain profile.      |
| `brand.support_email` | Support email on the domain profile.   |
| `brand.support_url`   | Support URL on the domain profile.    |
| `brand.legal_name`    | Legal name on the domain profile.     |

Use them anywhere you would have hardcoded your brand in the
template body:

```text
For help, contact {{brand.support_email}} or
visit {{brand.support_url}}.
```

This is why a leaked API key cannot impersonate someone else's
brand: the API key authenticates a team, but the `brand.*`
values come from a domain that team owns. If you've pinned the
API key to one specific domain, even another domain on the same
team is out of reach.

## Sandbox renders

In sandbox mode, the team isn't tied to a verified domain yet,
so `brand.*` resolves to a sandbox stub:

| Key                   | Sandbox value                    |
| --------------------- | -------------------------------- |
| `brand.name`          | Your team name + " (sandbox)"    |
| `brand.support_email` | `support@sandbox.zevsend.com`    |
| `brand.support_url`   | `https://sandbox.zevsend.com`    |
| `brand.legal_name`    | Your team name                   |

It's there so previews render and template wording is realistic
during development — your live messages always use your real
brand profile.

## Variable types

The variable manifest accepts these types:

| Type     | What it validates                                          |
| -------- | ---------------------------------------------------------- |
| `string` | Any value, coerced to string.                              |
| `number` | A numeric value or a string that parses as one.            |
| `email`  | Looks like an email address.                               |
| `phone`  | A phone number, normalised to E.164 where possible.        |
| `url`    | A valid `http(s)` URL.                                     |
| `code`   | A short alphanumeric token. Useful for OTPs.               |

Type validation runs before we hand the message off, so a
malformed value returns `422 invalid_variable` and the message
is never charged.