WarmblyDocs

Personalization & Expressions

Merge fields, conditionals, fallbacks, functions, spintax, and automation expressions: the templating language shared by campaign emails and automations.

Write one email and have it come out personalized for every contact, using merge fields like {{.FirstName}} plus helpers for fallbacks, conditions, and math. No programming knowledge needed; match the patterns below.

Quick reference

You want toWrite
Insert a first name{{.FirstName}}
Back up an empty field{{.FirstName | default "there"}}
Branch on a condition{{if eq .Company "Acme"}}...{{else}}...{{end}}
Randomize wording (campaigns only){a|b|c}
Filter an automation by a numbergtf .confidence 0.8

Variables

Contact fields (campaigns)

Exact names, capitalized, with a leading dot: {{.FirstName}}, {{.LastName}}, {{.Email}}, {{.Company}}, {{.Phone}}.

Hi {{.FirstName}}, I noticed {{.Company}} is hiring and wanted to reach out.

Field names are case-sensitive

{{.FirstName}} works. {{.firstname}}, {{.firstName}}, and {{.FIRSTNAME}} do not, and leave an empty value or a leftover token in your email.

Custom fields work the same way with a dot: {{.industry}}, {{.account_owner}}. Names containing spaces or dashes also work as written ({{if .job title}}...{{end}}) and resolve everywhere, including inside conditions and helpers, because Warmbly rewrites them to an index lookup for you.

Trigger variables (automations)

Automations carry the trigger's data rather than a contact's saved fields, keyed in lowercase and referenced with the required leading dot:

New reply from {{.contact_email}} on campaign {{.campaign_id}} ({{.intent}})
TriggerVariables
Reply receivedcontact_email, contact_id, campaign_id, intent, confidence, subject, snippet
Meeting booked / rescheduledinvitee_name, invitee_email, event_name, scheduled_for, join_url, source, contact_id
Meeting canceledSame, without join_url
Email bounced, Deliverability complaintcontact_email, campaign_id, contact_id, event_type, provider, reason
Unsubscribedcontact_email, contact_id, campaign_id, source
Warmup health changedemail, new_state, previous_state, reason
Campaign actioncontact_email, contact_id, campaign_id, campaign_name, first_name, last_name, company, phone

Action text is a full template, not token substitution: conditionals, pipelines, helpers, {{range}}, and nested fields ({{.lead.company}}) all work, and values keep their type. Because numbers stay numbers, {{if gt .confidence 0.8}} works directly in a message body. A malformed template never blocks the action; it falls back to plain {{.key}} substitution. Unknown keys render empty.

Fallbacks

Put the field first, then a pipe, then default and your backup in quotes:

Hi {{.FirstName | default "there"}},
Hope things are going well at {{.Company | default "your company"}}.

Conditionals

{{if}} includes text only when something is true, {{else}} handles the other case, and every {{if}} must close with {{end}}.

{{if .Company}}I see you're at {{.Company}}.{{end}}
{{if eq .Company "Acme"}}Great to connect with the Acme team.{{else}}Great to connect.{{end}}

eq is equals and ne is not-equals. Quote your text values.

Functions

The same helpers work in campaign emails, automation conditions, and action text.

MathMeaningStringWhat it does
add sub mul div modArithmeticlower upper titleChange case
num aForce a value to a numbertrimRemove surrounding spaces
containsContains, case-insensitive
hasPrefix hasSuffixStarts or ends with, case-sensitive

Division or modulo by zero returns 0 rather than erroring, so these never break an email.

Comparing numbers

  • Native: gt, lt, ge, le, eq, ne compare as-is, and work when the value is already a number (typical of automation data like confidence).
  • Coercing: gtf, ltf, gef, lef read both sides as numbers first. Use these when a value might be text (typical of contact custom fields).

Rule of thumb

Comparing automation numbers? Native gt / ge works. Comparing a custom field or anything that might be text? Use the f versions so "42" reads as 42. When in doubt, the f versions are safe.

{{if gtf .deal_size 10000}}Enterprise prospect{{end}}
{{if hasSuffix .Email ".edu"}}As someone in academia, {{end}}I thought this might help.

Spintax

Alternatives in single braces separated by pipes, so a batch of sends does not look identical:

{Hi|Hey|Hello} {{.FirstName}}, {hope you're well|hope things are good}.

Spintax rules

Campaign subject and body only; it does not run in automations. Use single braces, not the double braces of merge fields. Only groups containing a pipe count, so a brace run without one (CSS or JSON in an HTML email) is left exactly as written.

Advanced automation expressions

For anything the visual picker cannot express. An expression passes when it renders to something truthy; it is false when empty, false, 0, no, off, or <no value>, and a broken expression is false rather than silently passing.

Write it bare (recommended) or as a full template when you need {{else}}:

gt .confidence 0.8
{{if gt .confidence 0.8}}yes{{end}}
and (gtf .confidence 0.8) (eq .intent "positive")
or (eq .intent "positive") (eq .intent "neutral")
not (eq .intent "negative")
hasSuffix .contact_email "@acme.com"

and, or, and not take whole tests as inputs, so wrap each in parentheses when mixing groups.

Where each thing applies

SurfaceVariablesConditionals / functionsdefaultSpintax
Campaign subject & bodyContact fieldsYesYesYes
Automation conditionTrigger keysYesYesNo
Automation action textTrigger keys, with the dotYesYesNo

Gotchas

  • Standard fields are case-sensitive: {{.FirstName}}, not {{.firstname}}.
  • {a|b} is spintax (campaigns only); {{...}} is a variable, condition, or function.
  • Always close {{if}} with {{end}}, and quote text values.
  • If a comparison on a custom field behaves oddly, switch gt/lt to gtf/ltf, or wrap the value in num.
  • Unknown variables render empty rather than erroring. A leftover {{...}} usually means a typo or wrong capitalization.
  • In action text keep the leading dot. It is standard Go text/template, so a bare {{contact_email}} is invalid.

Preview and validation

Campaigns: the composer's live Preview renders against a sample contact exactly as the real send does (variables, conditionals, functions, then spintax). It flags errors like a broken {{if}}, which block launching, and unresolved tokens left after rendering.

Automations: an advanced expression is validated on save, so a condition that does not parse cannot be saved. The Reference button lists every variable and function inline.

On this page