Variables & personalization
Pass a variables object on a send and Anypost fills its values into the
subject, body, and headers before delivery.
The variables field
variables is an optional object on POST /v1/email. Write {{ key }}
markers anywhere in the message, and Anypost replaces each one with the
matching value from variables:
curl https://api.anypost.com/v1/email \
-H "Authorization: Bearer $ANYPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <[email protected]>",
"to": ["[email protected]"],
"subject": "Your {{plan}} plan is ready",
"html": "<p>Welcome to the {{plan}} plan. You have {{seats}} seats.</p>",
"variables": { "plan": "Pro", "seats": 5 }
}'That send arrives with the subject "Your Pro plan is ready". variables works
the same way whether the body is inline, as above, or comes from a template;
see Sending with templates.
Placeholder syntax
Markers use Handlebars: a key wrapped in double braces.
- Simple value:
{{plan}}resolves tovariables.plan. - Nested value:
{{order.total}}resolves tovariables.order.total. Objects may nest up to four levels deep. - Missing key: a marker whose key is absent from
variablesrenders as an empty string. It is not an error, and the marker is not left in the message.
A value is HTML-escaped by default, so one containing < or & is safe to
place in an HTML body. To insert a value as raw markup, use triple braces:
{{{ banner_html }}}. Reach for the triple-brace form only when the value is
trusted HTML you intend to render.
Conditionals and loops
The subject and body support Handlebars block helpers, so a message can show a
section conditionally or repeat one over a list. The data still comes entirely
from variables; the blocks only decide which parts of the message render.
Include a section conditionally with {{#if}}, optionally with an {{else}}:
{{#if order.gift}}
<p>A gift receipt is enclosed.</p>
{{else}}
<p>Thanks for your order.</p>
{{/if}}{{#unless}} is the inverse: it renders only when the value is falsy or absent.
Iterate over an array with {{#each}}. Inside the block, {{this}} is the
current item and {{@index}} is its zero-based position:
<ul>
{{#each items}}
<li>{{@index}}. {{this}}</li>
{{/each}}
</ul>When the array holds objects, reference each object's keys directly inside the
block: {{#each items}}{{name}}: {{price}}{{/each}}.
{{#with}} shifts the context into a nested object so you can drop the prefix:
{{#with order}}Order {{id}}, {{total}} total.{{/with}}To branch on a comparison rather than on presence alone, comparison helpers
such as eq and gt are available as sub-expressions:
{{#if (eq plan "Pro")}}...{{/if}}.
Per-recipient values: name and email
Two markers are filled from the recipient and need no entry in variables:
| Marker | Value |
|---|---|
{{name}} | The recipient's display name, or an empty string if the address has none. |
{{email}} | The recipient's email address. |
They differ per recipient within a single send, so one {{name}} in the body
greets everyone in to by their own name. If variables also defines name
or email, the recipient binding takes precedence.
The reserved unsubscribe_url variable
A send with unsubscribe.mode: "generate" fills one more marker,
{{unsubscribe_url}}, with that recipient's one-click unsubscribe link — the
same URL Anypost puts in the List-Unsubscribe header. You do not add
unsubscribe_url to variables; if you do, your value overrides the
generated link.
It is the one marker that renders even on a send with no variables object
(see Substitution is off unless you opt in
below). See Unsubscribe handling for the full
one-click setup.
Where substitution applies
When variables is present, Anypost renders markers in:
- the
subject, - the
htmlbody, - the
textbody, - the values of any custom
headers.
Marker-like text in attachments or in addresses is left alone.
Substitution is off unless you opt in
Anypost substitutes only when a request carries a non-empty variables object.
Send with no variables, or with variables: {}, and the message goes out
exactly as written: any {{ ... }} in the body stays as literal text.
This protects a body that carries its own braces, such as a code sample or
output from another templating system. It also means the {{name}} and
{{email}} bindings do nothing unless the request includes a non-empty
variables object.
One case overrides this: a mode: "generate" unsubscribe send. Anypost has to
render it to fill {{unsubscribe_url}}, so the templating step runs even with
no variables — and any other {{ ... }} in that message is evaluated along
with it. See Unsubscribe handling.
The opposite case is the one to watch. Once variables is non-empty, every
{{ ... }} in the rendered fields is treated as a marker, including any you
did not intend as one.
Limits
| Limit | Value |
|---|---|
| Total size | 64,000 bytes, JSON-encoded |
| Keys per object level | 100 |
| Nesting depth | 4 |
| Value types | Any JSON-serializable value |
A variables object that exceeds a limit is rejected with 422
validation_error; see API conventions.
Different values for each recipient
Within one POST /v1/email, every recipient shares the same variables
object. Only {{name}} and {{email}} change from one recipient to the next.
To give each recipient genuinely different values, such as an order number or
an account balance, send a batch. Every entry in a batch request carries its
own variables. See Batch sending.
Where to go next
- Sending with templates: store the body once and personalize per send.
- Batch sending: distinct
variablesfor each recipient in one request. - Send a single email: the full
POST /v1/emailrequest.