Email templates are filled in with a small template language. Values go in double braces, logic goes in {% %} tags, and filters after a | change how a value looks. The same language works in the subject, the HTML and the plain text of a template, and in messages you write in Compose.
This is the language for email templates. Flows have their own, simpler {{ … }} lookups for block settings, described in the block reference.

Where values come from

When a template is sent, it reads the values the sender passed: On top of those, app is always available from Settings → App details: If the data you pass contains its own app object, its keys win over the settings.

Values

Write a value’s path between double braces. Use dots to go inside objects:

Missing values

A missing top-level value renders as nothing: {{ nickname }} with no nickname gives an empty string, and so does a missing key on an object that exists ({{ user.nickname }} when user has no nickname). But going inside something that doesn’t exist is an error: {{ order.shipping.city }} when there is no order at all fails the send. Guard optional objects with a condition, or always pass them.

Filters

Add a filter after a |. Filters can be chained, and some take arguments in brackets. Plain default('x') only applies when the value is missing. An empty string is a value, so pass true as the second argument when empty should also fall back.

Conditions

You can compare with ==, !=, >, <, >=, <=, combine with and, or and not, and test membership with in: {% if 'admin' in user.roles %}. In the visual editor, the Condition block does the same: content only shown when a condition is true.

Loops

Inside a loop, loop.index counts from 1, loop.first and loop.last are true on the first and last item, and loop.length is the number of items. The {% else %} part runs when the list is empty. In the visual editor, the Repeat block makes one copy of its content per item in a list.

Escaping

Values are escaped: characters such as <, > and & are turned into their safe forms, so a value can never inject markup into your email. A customer named <script> appears as text, not as code. When a value really is HTML you trust (for example a formatted note your own system produced), use safe:
Never use safe on anything a user typed. That would let them put their own HTML, including links that look like yours, into your emails.

Escaping in subjects and plain text

Escaping applies everywhere, including the subject and plain text. A value such as Smith & Sons appears as Smith &amp; Sons there. For values that may contain &, <, > or quotes and appear in the subject or plain text, add | safe in those two places only. They aren’t HTML, so this is harmless there.

Comments

What the language can’t do

Templates run in a sandbox. They can read the values you pass and format them, but can’t read files, call other services, or reach anything else. Do that work before sending, in a flow, and pass the results as values.

Try it before you send

  • In the template editor, Preview renders with your sample values.
  • Send a test email sends it to you with the sample values.
A mistake in the template syntax (an unclosed {% if %}, an unknown filter) or a missing nested value makes the send fail, and Mail activity shows the error. Test with samples that are as complete as real data, and with samples where optional parts are missing.

A complete example

A receipt that handles an optional discount and any number of items:
Send it with data like: