Personalization variables
The names you may put in a campaign subject or body. Manyreach calls them variables (the web app inserts them from a Personalize menu). This page is the vocabulary; the send sequence is sending-a-campaign and what to actually write is writing-emails. How deeply to personalize — merge fields are only rung 1 of a four-rung ladder, and depth is an economics decision — is the playbook's Personalization levels and Economics of personalization.
The vocabulary is a closed list, not a rule you can apply. A variable is not the upper-cased prospect field:
custom1is written{{CUSTOM_1}}— with an underscore the field does not have — and plausible names like{{COMPANY_NAME}},{{WEBSITE}}and{{SENDER_NAME}}are rejected even though{{COMPANY}},{{DOMAIN}}and{{SENDER_FIRSTNAME}}are valid. Take names from the table below; anything invented fails at start with a 422.
Write them with double braces, no inner spaces, in the exact case shown: {{FIRST_NAME}}. They
work in the subject and the body, on the campaign itself and on every followup. Nothing is checked at
create or patch — only campaign/start_by_id validates them.
The confirmed variables
| variable | fills in | from |
|---|---|---|
{{FIRST_NAME}} |
first name | prospect firstName |
{{LAST_NAME}} |
last name | prospect lastName |
{{FULL_NAME}} |
full name | no single prospect field holds this |
{{EMAIL}} |
email address | prospect email |
{{COMPANY}} |
company name | prospect company |
{{COMPANY_SIZE}} |
employee count or size band | prospect companySize |
{{INDUSTRY}} |
industry | prospect industry |
{{JOB_POSITION}} |
job title | prospect jobPosition |
{{CITY}} |
city | prospect city |
{{STATE}} |
state or province | prospect state |
{{COUNTRY}} |
country | prospect country |
{{LOCATION}} |
location or address | prospect location |
{{PHONE}} |
phone number | prospect phone |
{{DOMAIN}} |
company domain | prospect domain |
{{ICEBREAKER}} |
the prepared opening line | prospect icebreaker |
{{CUSTOM_1}} … {{CUSTOM_20}} |
the twenty free-text fields | prospect custom1 … custom20 |
{{SENDER_FIRSTNAME}} |
the sending account's first name | sender firstName — note: no underscore before NAME |
{{SENDER_SIGNATURE}} |
the sender's signature block | sender signature |
{{UNSUBSCRIBE_LINK}} |
a ready-made link reading "Unsubscribe" | — |
{{UNSUBSCRIBE_URL}} |
the bare unsubscribe URL, for your own link text | — |
Prospect fields: schemas/prospect.
This table is what has been verified, not a guarantee of completeness. The validator never lists its own vocabulary, so a name that is absent here is unverified rather than proven invalid — only its answer at start settles it.
Custom variables
{{CUSTOM_1}} … {{CUSTOM_20}} fill custom1 … custom20. Two things follow:
- The underscore belongs to the variable, not the field.
custom1→{{CUSTOM_1}}.{{CUSTOM1}}is rejected, and since that name exists in no casing the error offers no correction. (Published help material writes the defaults asCustom_1; that casing is rejected too, withPlaceholder has to match correct case. Change {{Custom_1}} to {{CUSTOM_1}}.) - These slots can be renamed in the web app, and
{{CUSTOM_1}}…{{CUSTOM_20}}keep working regardless. A renamed slot can additionally be written by its new name — a slot renamed toProjectNamealso accepts{{ProjectName}}— but no operation exposes an account's variable names, so you cannot discover them here. Use the numbered form: it is the one that holds whatever the account has been renamed to. Only use a name if your user gives you one, and if start rejects it, fall back to the number.
Names that look right and are not
{{NAME}}, {{COMPANY_NAME}}, {{JOB_TITLE}}, {{TITLE}}, {{WEBSITE}}, {{URL}},
{{PROSPECT_ID}}, {{SIGNATURE}}, {{UNSUBSCRIBE}}, {{SENDER_NAME}},
{{SENDER_FIRST_NAME}}, {{CUSTOM_0}} and {{CUSTOM_21}} are all rejected. The sender family
is the sharpest trap: {{SENDER_FIRSTNAME}} is valid while {{SENDER_NAME}} is not, and the
prospect's own first name does carry the underscore ({{FIRST_NAME}}) — the two families follow
different conventions.
Some prospect fields have no known variable at all: every plausible name for website
({{WEBSITE}}, {{WEBSITE_URL}}, {{COMPANY_WEBSITE}}, {{URL}}), for companySocial and
personalSocial, and for screenshotUrl / logoUrl / customImageUrl is rejected. Copy such a
value into a custom field if the email needs it.
What the validator tells you
campaign/start_by_id answers 422 and names every offending
expression in one response, grouped under Fix Subject: and Fix Body: — one round trip is enough to
correct them all. There are two messages:
| message | meaning | do this |
|---|---|---|
Wrong placeholder: {{X}} |
no variable of that name exists, in any casing | replace it with a name from the table, or delete it |
Placeholder has to match correct case. Change {{x}} to {{X}}. |
the name exists; only the casing is wrong | use the exact form the message names |
Nothing was sent. Fix the copy with campaign/patch_by_id (campaign subject/body) or followup/patch_by_id (a followup's), then start again.
Fallbacks for missing data
A variable with no value on the prospect leaves a hole in the sentence. Give it a fallback with the
Liquid default filter, in straight ASCII quotes:
Hi {{FIRST_NAME}}, quick question about {{COMPANY | default: "your team"}}.
A
|turns the check off. Any{{ … }}expression containing a pipe is skipped by start-time validation entirely:{{ZZ_BOGUS}}is rejected,{{ZZ_BOGUS | default: "x"}}passes, and so does a mis-cased name that would otherwise have been corrected for you. Get the spelling right from the table first, then add the filter — otherwise the mistake reaches the send instead of coming back as a 422.
default: and newline_to_br are the only filters the product documents; treat any other as
unverified.
Conditionals
Liquid tags carry conditional copy. Inside {% %} the variable is written bare, without braces —
braces are only for output:
{% if COMPANY %}I saw {{COMPANY}} is hiring{% else %}I saw your team is hiring{% endif %}
Available: {% if %} / {% else %} / {% endif %}, {% unless %}, {% case %} / {% when %}, and
{% for %}; comparisons == != > < >= <=, plus and, or, contains. String literals
take " or '.
Start-time validation skips {% %} tags but still checks plain {{ … }} expressions inside them,
so a name in conditional copy must still be real. Whether conditionals are honoured in the subject
is unverified — the published examples are all body copy.
A/Z (A/B) variants
Alternative wordings go in a spin tag: single braces, the literal word abtest, pipe-separated
variants, and a trailing pipe before the closing brace.
{abtest|quick question|{{FIRST_NAME}}, quick question|}
It works in subject and body, on a whole message or a single phrase, takes as many variants as you like, and variables nest inside it. Variants are combined at random per send — step 1's variant A does not pair with step 2's variant A. The web app's campaign report shows every combination; the API carries no per-variant breakdown (campaign/stats_by_id), so don't go looking for one. Single-brace forms are never examined by start-time validation.
When a prospect is missing a value
deactivateIfMissingPlaceholder on the campaign decides what happens to a prospect with no value for
a variable the copy needs:
true— the prospect is deactivated instead of mailed, and carriessendingStatus: MissingPlaceholder(schemas/prospect); the web app labels that state "Missing Tag". Filling the field (prospect/patch_by_id) makes it sendable again.false— the prospect is not deactivated and the send goes ahead.
Either way, a | default: fallback or an {% if %} is what keeps incomplete records in the send with
copy that still reads properly.
Not a variable: spintax
Single braces with pipes — {|Hi|Hello|Hey|} — are spintax, a different mechanism that picks one
option per message rather than substituting prospect data. The two compose (a variable can sit inside
a spin option), and both are validated when the campaign starts. See spintax —
which also covers the trap that makes a stray { or } disappear from the delivered copy.