> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pulse-send.jiordiviera.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Templates

> Keep email content in PulseSend, send it by slug with your data, and change it without redeploying.

A template holds the subject, HTML and text of an email. Your code sends its slug and the data; your team edits the content in the dashboard. Emails only change when someone publishes a new version.

```bash theme={"dark"}
curl https://pulse-send.jiordiviera.me/api/v1/emails \
  -H "Authorization: Bearer $PULSESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "jane@example.com",
    "template": "otp-login",
    "data": { "code": "482913", "minutes": 10 }
  }'
```

With `template`, leave out `html` and `text`: they come from the template. `subject` and `from` are optional and replace the template's when you send them. Attachments, tags, headers and idempotency keys work as usual.

## Create a template

In the dashboard, open **Templates** and click **New template**. Start from a ready-made design (verification code, welcome, password reset, receipt, invoice, invitation, sign-in alert) or from your own HTML.

You can also create one with the API:

```bash theme={"dark"}
curl https://pulse-send.jiordiviera.me/api/v1/templates \
  -H "Authorization: Bearer $PULSESEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sign-in code",
    "slug": "otp-login",
    "subject": "Your {{brand.name}} code: {{code}}",
    "from": "Acme <security@acme.com>",
    "html": "<p>Your code is <strong>{{code}}</strong>. It expires in {{minutes}} minutes.</p>"
  }'
```

Pass `"starter": "otp"` instead of `html` to start from a ready-made design. Starters: `otp`, `welcome`, `password-reset`, `receipt`, `invoice`, `invitation`, `sign-in-alert`.

## Slugs

The slug is what your code sends: lowercase letters, digits and hyphens, 3 to 64 characters, unique in your team. Without one, PulseSend makes it from the name.

When you rename a slug, the old one keeps working as an alias, so code already deployed doesn't break. You can also use the template ID (`tpl_…`) anywhere a slug is accepted.

## Drafts and versions

You always edit a draft. Nothing can be sent with a template until its first version is published, and emails keep using the published version while you edit.

Publishing (in the dashboard, or `POST /templates/{slug}/publish`) turns the draft into the next version, v1, v2 and so on, and every email switches to it right away. To keep a call on a known version, pin it:

```json theme={"dark"}
{ "template": "otp-login", "version": 2, "data": { "code": "482913" } }
```

The last 50 versions are kept. Restoring an old one copies it into the draft: publish to make it live again.

## Syntax

Templates use [Mustache](https://mustache.github.io/mustache.5.html). Subject, HTML and text all accept it.

| Write | To |
| - | - |
| `{{name}}` | Insert a value, HTML-escaped |
| `{{{html}}}` | Insert a value as HTML, without escaping |
| `{{user.first_name}}` | Read a nested value |
| `{{#items}}…{{/items}}` | Repeat for each item of a list, or show if truthy |
| `{{^items}}…{{/items}}` | Show when the value is missing, false or empty |

Partials, blocks, pragmas and delimiter changes are not supported. A template that doesn't parse is refused when you save it, with `template_syntax_error` and the line.

Inside `{{#items}}`, names refer to the current item, so a receipt can list its lines:

```html theme={"dark"}
{{#items}}
<tr>
    <td>{{label}}</td>
    <td>{{amount}}</td>
</tr>
{{/items}}
```

```json theme={"dark"}
"data": { "items": [{ "label": "Team plan", "amount": "$24.00" }, { "label": "2 seats", "amount": "$16.00" }] }
```

## Variables

PulseSend finds the variables in the subject, HTML and text. For each one, choose in the editor (or with `variables` in the API):

* **Required**: the send fails with `422 template_variable_missing` if `data` doesn't have it, so nobody gets "Hello ,".
* **A default value**: used when `data` doesn't have it.
* **An example**: used only for the preview and test sends.

New variables are required until you say otherwise.

## Team brand

Set your logo, color and postal address once in **Settings › Team**. They are always available as `{{brand.name}}`, `{{brand.logo_url}}`, `{{brand.color}}`, `{{brand.on_color}}` (black or white, whichever reads on your color) and `{{brand.address}}`. You never send them in `data`.

## Preview and test

`POST /templates/{slug}/render` returns the subject, HTML and text a send would produce, without sending anything. It renders the draft, using examples for missing variables; pass `"version": 3` to render a published version with the same checks as a real send.

In the dashboard, **Send a test** sends the draft with its examples to a member of your team. It counts as a live email.

## Plain text

Without a text version, PulseSend writes one from the HTML: headings and paragraphs on their own lines, links as `label (url)`. Write your own in the **Text** tab or in `text` to replace it.

## Limits

| Limit | Value |
| - | - |
| Templates per team | 100, or 10 on the [free plan](https://docs.pulse-send.jiordiviera.me/guides/limits.md#free-plan) |
| HTML | 512 KB |
| Versions kept | 50 |

Scopes: `templates:read` (list, read, render, versions) and `templates:write` (create, edit, publish, delete). New keys get both.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.