Skip to Content
ConceptsVariables

Variables

Templates carry {{token}} placeholders in both the subject and the HTML body. At render time each one is replaced with a value from the variables object you pass to send().

Template
Subject: Your order {{order_id}} is on its way Body: Hi {{first_name}}, it should reach you by {{delivery_date}}.
Your code
await letters.send({ template: 'order-shipped', to: customer.email, variables: { first_name: 'Sagar', order_id: 'A-1042', delivery_date: 'Friday', }, })

Token syntax

A token is {{, an optional run of spaces, the name, optional spaces, }}. Names may contain letters, digits, underscores and dots.

Written in the templateMatches
{{first_name}}
{{ first_name }}✅ — surrounding spaces are ignored
{{user.name}}✅ as a literal key — see below
{{first name}}❌ — spaces inside the name aren’t allowed

Dots are part of the name, not a path. {{user.name}} looks up the key "user.name" — it does not walk into a nested object. Pass { "user.name": "Sagar" }, or flatten to {{user_name}}, which is what the built-in library packs do.

Missing variables stay visible

A token with no matching value is left in the output exactly as written, rather than replaced with an empty string:

await letters.send({ template: 'order-shipped', to: customer.email, variables: { first_name: 'Sagar' }, // order_id and delivery_date missing })
What gets sent
Subject: Your order {{order_id}} is on its way Body: Hi Sagar, it should reach you by {{delivery_date}}.

This is deliberate. A blanked token produces a sentence that reads fine and is quietly wrong — “it should reach you by .” — while a visible {{delivery_date}} gets reported the first time it goes out. Nothing throws; the send succeeds.

null and undefined count as missing and leave the token in place. Everything else is stringified: 0 renders 0, false renders false, an array renders as its comma-joined contents.

Nothing validates that you supplied every token a template uses — the API never sees a list of what the template needs. If you want that guarantee, keep a typed object per template on your side and build variables from it.

Values are inserted as-is

Substitution is a literal string replacement into the HTML body. Values are not HTML-escaped, so a value containing < or & lands in the markup verbatim.

Treat variables as trusted content: pass names, order ids and dates you control. Don’t pass raw user-authored text — a display name someone typed, the body of a support ticket — straight into a template without escaping it yourself first.

Recording expected variables

Templates imported from the library arrive with a variablesSchema on the locale row, listing the tokens that pack’s design uses. It’s informational — the dashboard reads it to show you what to fill in, and the render path ignores it entirely.

Templates you build yourself don’t get one; the editor is the record of what the template expects.

Last updated on