Skip to content
Playbook

Email personalization at scale (without the cringe)

A merge tag you cannot fill is a segment you should not send. The template language, the render order, coverage arithmetic and how a blank renders.

30 Apr 2026 12 min readBy Autocloz Editorial, GTM team
Email personalization at scale (without the cringe)

Personalisation at scale is a rendering problem sitting on top of a data problem, and almost all the pain is in the second one. The template language is small and learnable in an afternoon: variables, dotted paths, fallbacks, conditionals, and a way to vary phrasing. What decides whether the output reads as written-for-me or as mail-merge is whether the field you built the sentence around is actually populated for the rows you are sending to. A merge tag you cannot fill is a segment you should not send, and the arithmetic of that is the useful part of this article.

The variables that actually exist, and one that will surprise you

You cannot personalise on a field the renderer never receives, so start with the context rather than with the copy. Autocloz builds the merge context for a lead from a single joined query and hands the template exactly this:

  • email — the lead's primary address.
  • first_name, last_name, full_name, title — from the person record.
  • company.name, company.domain, company.industry — from the joined company, addressed with a dot.
  • every key in the lead's custom_fields, merged in at the top level.

Two behaviours in that list are worth designing around.

first_name is never blank. It is set to "there" when the person record has no first name, and a second fallback re-applies the same default if the lead has no person at all. So Hi {{ first_name }}, degrades to Hi there, rather than to Hi ,. That protection does not extend to anything else — last_name, title and every company field render as an empty string when missing.

Custom fields are merged last and are addressed at the top level. A custom field called industry_angle is {{ industry_angle }}, not {{ custom.industry_angle }}. The consequence of merging last is that a custom field named first_name, email or title overwrites the core value. That is occasionally useful and more often an accident, so avoid reusing core names as custom field keys.

The template language, in one pass

Everything the renderer understands, with what each construct is for.

Scalars and paths. {{ first_name }} and {{ company.name }}. A path is walked segment by segment, and a segment that does not resolve returns an empty string rather than an error. Paths beginning with a double underscore are refused outright as a defence against a merge expression walking into runtime internals.

Filters. Chained with pipes and applied left to right: default: "there", capitalize, upper, lower, title, strip. Two extra behaviours are worth knowing. raw and safe are opt-outs from HTML escaping for a value you vouch for. And an unrecognised bare segment is treated as an implicit default{{ first_name | there }} produces there when the value is empty and the value unchanged when it is not — which matches the syntax people arrive with from other tools.

Conditionals. {% if title contains "VP" %}…{% elif title contains "Director" %}…{% else %}…{% endif %}. The expression evaluator is deliberately weak: it supports ==, !=, contains, and bare truthiness against a path, and nothing else. That is a safety property, not a missing feature — a template language with a real expression evaluator is a code-execution surface, and nobody needs arithmetic inside an email. Nested blocks resolve outside-in over a capped number of passes.

Spintax. {a|b|c} picks one branch. A branch may be empty, so {Just checking in|} is a legitimate way to include a phrase half the time. A brace with no pipe inside it is not spintax, so a literal {tbd} in your copy survives untouched. The choice is seeded from the send identifier, which makes it deterministic: a retried send picks the same branch, so a variant's statistics do not drift when a message is re-dispatched. The glossary entry on spintax covers the syntax on one screen.

The order these run in is not arbitrary and it fixes two real bugs. Conditionals resolve first, because their branches contain variables and spintax. Then every {{ … }} expression is masked out before spintax runs, and restored afterwards. Without the mask, a filter pipe inside a variable — {{ first_name | there }} — contains an inner { first_name | there } that looks exactly like spintax, and a merge *value* containing braces and a pipe would be mangled as spintax after substitution. Variables render last, which is the only ordering that makes both impossible.

What a blank actually looks like in the delivered message

This is the part that produces the cringe, and it is diagnosable from the text a recipient received.

Hi , — an empty first_name, which in Autocloz means the template used a variable that is not first_name, since that one has a code-level default. More usually this comes from a custom field.

A double space mid-sentence — an empty variable between two words. I noticed is hiring is {{ company.name }} rendering blank. This is the single most common personalisation failure and it is invisible in the template.

A trailing prepositionI saw your work at . The sentence was written assuming a value that 30% of the list does not have.

A literal {{ industry }} in the body — the field name does not exist in the merge context at all. Autocloz's spam-scoring pass catches this specific case before send, adding 25 points under the label unrendered_merge_tag with the suggestion to check merge-tag names against your lead fields; the spam checker runs the same rules on a draft.

An identical opening line across a whole segment — the variable was blank for every row in it, and the fallback filled in. This is the failure that looks like success, because nothing is broken and every message reads fine in isolation.

The general rule that prevents all five: any variable that appears mid-sentence carries a fallback, and any variable whose absence would break the sentence belongs inside a conditional rather than inline. {% if company.name %}I saw {{ company.name }} is hiring.{% endif %} deletes the sentence rather than mutilating it.

Coverage arithmetic: size the field before you write the copy

Here is the discipline almost nobody applies, and it takes ten minutes.

Before writing a line that depends on a variable, count how many rows in the segment actually have it. Not the list — the segment you are about to send to. Then decide, per variable, what happens to the rows that do not.

An illustrative worked example on a 4,000-row segment. Substitute your own counts; the shape is what matters.

  • first_name: 3,880 populated (97%). Safe inline; the remaining 3% get there.
  • company.name: 3,640 (91%). Safe inline with a fallback of "your team".
  • title: 2,910 (73%). Not safe inline. A quarter of your list gets a broken sentence. Use it inside a conditional, or use it only to pick a variant rather than to appear in the text.
  • company.industry: 2,200 (55%). Not usable in prose at all. Usable as a conditional selector for which paragraph runs.
  • A custom recent_trigger field: 620 (16%). This is not a variable, it is a segment. Split those 620 rows into their own campaign with copy written around the trigger, and send the other 3,380 a message that never mentions it.

That last line is the whole argument. A field populated on a minority of your list is a segmentation instruction, not a merge tag. Trying to serve both populations from one template produces either a broken sentence for the majority or a bland one for everybody. Splitting produces two honest messages. How to decide which axes are worth splitting on is covered in segmenting your CRM and lists, and how to get the fields populated in the first place in building a targeted B2B lead list.

Two more rules from the same arithmetic. Never build a subject line on a variable below about 95% coverage, because the subject is the one field with no room to degrade gracefully — and Autocloz refuses to send a message whose rendered subject is empty, recording it as errored rather than delivering a blank-subject email. And re-run the count before every send rather than once, because enrichment coverage drifts as rows are added.

Escaping, and the one place it does not happen

A merge value is data written by somebody else, and it lands inside HTML. Autocloz HTML-escapes substituted values in the body and the signature, so a company genuinely called "A & B Corp" renders correctly instead of corrupting the markup, and a hostile value cannot inject script. Template literal markup is untouched — only the substituted value is escaped — and a value you vouch for can opt out with | raw.

The subject line is deliberately not escaped, because it is plain text and escaping it would put a literal & in front of the recipient. That asymmetry is correct and it means the subject and the body have different safety properties. Do not paste HTML into a subject template.

AI rewriting: what it changes and what it cannot invent

Autocloz's autopilot ships with automatic personalisation switched on, and the rewriting endpoints run on your own OpenAI, Anthropic or Groq key with no per-lead metering, so the ceiling on quality is the model you point it at rather than a plan tier.

Mechanically, the endpoint takes a base subject and body plus the recipient's details, wraps every untrusted span in explicit fences, and asks for a rewrite that keeps the same length and the same call to action while adding one specific detail drawn from the recipient information. The fencing matters: a lead record can contain text somebody else wrote, and a rewriting prompt that concatenates it unfenced is a prompt-injection surface.

Per-channel constraints are applied rather than assumed. An SMS rewrite is held under 160 characters with one clear ask and no markdown; a LinkedIn message under 600 characters; a WhatsApp message to one to three short sentences with no emoji; a call opener to two or three sentences a rep can say out loud.

Three limits to hold on to. A model cannot invent a fact it was not given, so an empty context field produces fluent generality rather than relevance. A rewrite changes phrasing, not truth — if the base message overclaims, the rewrite overclaims more smoothly. And the output still passes through the same rendering pipeline, so a rewritten body containing braces and pipes will be read as spintax unless it was masked, which is one more reason to review rewrites before they enter a template. The adjacent craft of making a draft read in your own voice rather than a model's is worked through in AI replies that sound like you.

Autocloz's free plan covers 5 users and 10 mailboxes with merge variables, conditionals and spintax in the email sender and bring-your-own-key AI on top — start free and render a preview against ten real leads before you enrol anyone.

The subject line has a legal constraint, not just a stylistic one

Personalisation makes subject lines more tempting and more dangerous, because a merged subject can imply a relationship that does not exist.

The US CAN-SPAM Act, as summarised in the FTC's compliance guidance, requires that "the subject line must accurately reflect the content of the message", that the message discloses clearly and conspicuously that it is an advertisement, and that it carries a valid physical postal address. The FTC's own guidance puts the penalty at up to $53,088 per individual email in violation. A merged subject reading Re: our conversation, {{ first_name }} when there was no conversation is not a clever pattern-interrupt; it is a false header in the sense the statute means.

The safe forms are the boring ones: a subject that names what the message is about, optionally with a value that is true. A recipient's own company name is fine. A fabricated thread reference is not. The wider question of what makes a subject open-worthy is covered in sales email subject lines that get opened.

Diagnosing a personalisation failure

Work backwards from the delivered message rather than forwards from the template.

Step 1 — read ten sent messages, not the template. The template is what you intended. The sent copy is what happened. Autocloz stores the rendered body per send, which is the artefact to look at.

Step 2 — count the specifics. In those ten, how many contain a fact that could only be true of that recipient? If fewer than half, the problem is data coverage, not copy.

Step 3 — grep the sent bodies for the failure fingerprints. A double space, a comma immediately after a greeting, a full stop preceded by a space, a literal double brace. Each maps to a specific empty variable.

Step 4 — check the field, not the template. For whichever variable failed, count populated rows in the segment. If coverage is under 90%, the fix is a conditional or a split, not a better fallback.

Step 5 — only then look at the writing. Most personalisation complaints are data failures wearing a copy failure's clothes, and rewriting the sentence around a variable that is empty for 30% of the list changes nothing. Worked examples of what a genuinely specific first line looks like are in cold email personalisation examples, and if you are comparing an in-workspace merge layer against a dedicated enrichment tool, the Autocloz and Clay comparison sets out both models.

What personalisation at scale does not do

Four limits worth being explicit about.

It does not verify that a value is correct. The renderer inserts whatever is in the field. A stale job title from an eighteen-month-old export renders with complete confidence, and the recipient reads it as evidence you did not check.

It does not make a message relevant. A correctly merged sentence about a company that has no use for your product is a well-formatted irrelevance. Relevance is a targeting decision that happens before any of this.

It does not survive a template edited outside the tool. A body pasted from a word processor frequently carries smart quotes and non-breaking spaces that break a merge expression silently, and the first sign is a literal double brace in a delivered message.

It does not improve deliverability. Varying the text across sends is sometimes described as a placement tactic, and there is no public documentation from Google, Microsoft or Yahoo supporting the idea that phrasing variance is what their filters measure. Authentication, complaint rate and list quality are what those systems say they measure. Spintax is worth using for variety and for keeping variants comparable; it is not a filter-evasion technique, and treating it as one is how a sender ends up optimising the wrong thing for a quarter.

Frequently asked

What happens when a merge field has no value for a lead?

In Autocloz an unresolvable path renders as an empty string rather than raising an error, so the sentence around it ships with a hole in it. Two fields are protected by defaults set in code — first name falls back to "there" and the company object always exists with empty strings inside it — and everything else renders blank. The fix is a fallback filter on every variable that appears mid-sentence, written as first name pipe default colon quoted-word.

Is spintax the same thing as personalisation?

No. Spintax picks one of several fixed phrasings, so it varies the message across recipients without knowing anything about any of them. That is useful for keeping a high-volume sequence from being byte-identical across thousands of sends, and it is useless for relevance. Autocloz seeds its spintax choice deterministically from the send identifier, so a retry of the same send produces the same variant and A/B analytics stay aligned.

Should merge tags go in the subject line?

Only where the value is reliably present and the result reads naturally, because subject-line merges fail loudly. Autocloz renders the subject as plain text with no HTML escaping, and refuses to send at all if the rendered subject is empty, recording the send as errored instead. The bigger constraint is legal: the US CAN-SPAM Act requires that the subject line accurately reflects the content of the message, so a merged subject that overstates the relationship is a compliance problem rather than a style one.

How much personalisation is enough?

One accurate, specific fact that could only apply to this recipient outperforms five merge fields that could apply to anyone. The practical test is to read the message as though you were the recipient and ask whether any sentence would be false if the name at the top were swapped for a different one. If none would, the message is not personalised regardless of how many variables it contains.

Can AI write the personalised line for every lead automatically?

It can draft one from context you supply, and the quality tracks the context rather than the model. Autocloz's rewriting endpoint fences the base message and the recipient details as untrusted content before sending them to your own OpenAI, Anthropic or Groq key, applies per-channel constraints such as keeping an SMS under 160 characters, and returns a rewritten message. It cannot invent a fact about a recipient it was never given, and a model asked to personalise from nothing will produce fluent text that says nothing.

How do you spot a personalisation failure after the message has gone out?

Look for the fingerprints in the delivered text rather than in the template. A double space or a comma directly after a greeting means an empty merge value; a literal double-brace sequence in the body means a field name that does not exist in the merge context; a repeated identical opening line across a whole segment means a variable that was blank for every row in it. A spam-scoring pass that flags unrendered merge tags catches the second class before send.

Share
Free to start

Stop reading. Start sending.

Every tactic in this article is implemented behind the Autocloz dashboard.