Merge tags and personalization
How a variable resolves at send time, and in what order.
Pombo uses real Liquid: {{ variable }} for values, and {% if %} for
conditional content (see below). In the editor, a {{ variable }} shows as a
pill; underneath it's just a variable reference filled in per recipient.
Where values come from
Highest priority wins:
- Per-send override. The "Send test" modal in the app, or the
variablesargument tosend_test_emailandsend_transactional_email. - Event property.
{{ event.* }}only, from the payload of the event that triggered the send. A separate namespace:{{ event.amount }}never falls through to a contact field calledamount. - Contact field. The recipient's own row, including custom fields.
- Template baked value. A
variablesmap saved on the template, for things that are the same for everyone on a given send (an issue number, a campaign code). - Inline default.
{{ first_name | default: "there" }}renders "there" when nothing better is available.
Contact fields
{{ first_name }}, {{ last_name }}, {{ email }}, {{ phone }}, and any
custom field resolve from the contact. An unfilled tag
with no default renders as a visible marker in previews so you notice it.
Event data
A send triggered by POST /api/v1/events carries that event's properties.
Read them as {{ event.amount }}, {{ event.receipt_url }}, and so on, plus
{{ event.name }} for the event's own name. This works in
event-triggered sequences and
event-triggered transactional email.
Conditionals: only show something when a value is there
An optional property, a coupon code or a referrer's name, is either there or
it isn't, and copy that's only correct one of those ways reads broken the
other. Wrap the optional part in {% if %}, and compare against blank:
Thanks for your gift{% if event.amount != blank %} of {{ event.amount }}{% endif %}!
This reads "Thanks for your gift of $50!" when the amount is set, and "Thanks for your gift!", not "Thanks for your gift of !", when it isn't.
Write != blank, not a bare {% if event.amount %}. In Liquid an empty
string is true, so a bare {% if %} only catches a property that is missing
altogether. One that arrives present but empty, which is what an event
property usually is, takes the branch anyway and you get "Thanks for your gift
of !". != blank covers empty, missing and null alike.
{% unless %} works the same way. Type the tag text straight into the block
editor like any other text; there's no separate toolbar button for it yet.
One limit to know about before you lean on it. The block editor escapes " and
> on the way into the saved template, and the escaping does not stop at
Liquid's braces, so {% when "gold" %} and {% if event.count > 5 %} are not
read the way you typed them. Neither raises; the email just says the wrong
thing (#891). Comparisons against blank use neither character, which is why
they are the form recommended here. If you need {% case %}, a quoted value or
a numeric comparison, write that email as raw MJML, where the text reaches
Liquid exactly as typed.
Before you rely on a conditional in production, use "Send test" with the
value left blank to see the no-value case. If a value is blank rather than
missing entirely, it renders as nothing rather than a [tag] marker, so it's
easy to miss a broken sentence by eye, so send_test flags likely leftover
punctuation (a stray , ! or similar) so you catch it there.
System tags
{{ unsubscribe_url }} is injected per contact; you don't set it. The footer
references it automatically on marketing mail.