{{ ... }}outputs a value, optionally passed through filters.{% ... %}runs logic: conditionals, loops, and special tags.[Bracket]placeholders from templates you paste in from another tool still work.
Variables
Reference a value with double braces. Values come from namespaces:person: the contact
Field names accept
snake_case or camelCase, so person.first_name and person.firstName are the same.
person.custom: stored properties
Anything you’ve stored on a contact, including custom event attributes, is available under person.custom:
LoyaltyTier must match the stored key. For a property whose name has spaces, use the lookup filter:
organization: your workspace
event: flow and catalog data
event.* holds data supplied by a triggering event, such as cart items or recommended products. It powers catalog loops. In a one-off campaign blast there’s no event, so event.* is empty and any catalog loop renders its {% empty %} branch.
Campaign and links
Filters
Pass a value through filters with the pipe|. Chain as many as you like. Filter names are case-insensitive, and an argument uses the colon form.
Unknown filters pass the value through untouched, so a typo like
| uppercas silently does nothing. Check rendered output rather than trusting the syntax.
Conditionals
Use{% if %} with {% elif %}, {% else %}, and {% endif %}:
- Comparisons:
==,!=,>,<,>=,<=, andin(array membership or substring). Operands compare as numbers when both are numeric, otherwise as strings. - Boolean logic:
and,or,not. - Truthiness of a bare value: a non-empty array is true; a blank string, missing value, or numeric
0is false.
The keywords
if, elif, else, endif, for, and endfor are lowercase only. and, or, not, and filter names are case-insensitive.Loops
Iterate over a list with{% for %}, most often over event data like a cart or a product feed:
forloop object is available:
{% empty %} branch renders when the list is empty or missing. Loops nest, and conditionals, currency, and tag links all work inside them.
Special tags
Currency
{% currency_format %} formats a number as currency (workspace currency, defaulting to USD):
Unsubscribe and preferences
These resolve only at real send time, so they never block a send by being blank in preview.
Click-to-tag links
{% tag_link %} renders a link that tags the recipient in your Audience when they click it. It’s how you turn an email into a one-click preference capture (“which chain do you use?”, “pick your favorite collection”) that feeds straight into a segment.
tag to their contact (creating it in the Audience if it’s new), records the click, and forwards them to url. The tag is then usable as a segment.
You can drive the links from a loop, so a catalog becomes a row of choices:
A
tag_link only becomes a real click-to-tag link during an actual send (or a test send). In a plain preview with no delivery, it degrades to just its link text. The url must be http or https.Imported templates
Templates pasted from another email tool often use square-bracket placeholders like[First Name]. These still work, rewritten to the matching variable before rendering:
- Matching ignores case, spaces, underscores, and hyphens, so
[First Name],[first_name], and[FIRSTNAME]are equivalent. - Name-style brackets (
[First Name],[Name],[Greeting]) resolve to the blank-safe greeting, never a raw first name. - An unrecognized label is left exactly as written, so
[Product Name]stays literal rather than becoming a broken variable. - A
[Label](url)markdown link is left alone.
Missing values block sends
A{{ ... }} variable that resolves blank without a default filter is recorded as a missing field, and missing fields fail validation at launch. A misspelled variable path counts as missing too, since it resolves to nothing.
Unresolved {% ... %} tags and [Bracket] labels are different: they’re left literal and never block a send. Unsubscribe and preference links are also exempt, since they only resolve during a real send. Your workspace postal address is the one field you should set rather than default, since the compliance footer needs it.
Test sends are tag-aware
A test send now renders against a real delivery, so{% tag_link %} links are live. Clicking a link in a test email tags the actual contact and the tag appears as a segment, letting you confirm click-to-tag end to end before you launch. Test emails are prefixed with [TEST].
Escaping and raw output
Interpolated{{ }} values are HTML-escaped in the email body by default, so recipient data can’t inject markup. Your own template markup is never escaped. Use the raw filter to opt a value out of escaping, and only for content you control.
Writing checklist
- Subject matches the intent that triggered the send.
- Preview text supports the primary action rather than repeating the subject.
- One main call to action.
- Every optional variable has a
default, or uses a blank-safe token likegreeting_name. - If you use
{% tag_link %}, test-click it and confirm the tag lands in the Audience. - Links are absolute, and any UTM parameters you care about are set deliberately.

