Product guide
Templates and Liquid
Automation steps, announcements and scheduled messages are Liquid, the same syntax as Shopify and Mailchimp, and they all use the variables below. Each variable is filled in for each person at the moment their message is sent. Saved templates are the ones you keep.
A message typed into a conversation, or sent with POST /v1/messages, goes exactly as written: its braces are not filled in.
Liquid, not a bespoke syntax
{% if %}, {% for %}, | upcase and | default: all work the way they do everywhere else.
A missing variable renders as nothing
Never as its own source text. A message reading “Hi {{contact.name}}” does not look like a missing field; it looks like a company that cannot operate its own software.
One renderer
The preview calls the same function the sender calls. A preview with its own copy of the rules eventually disagrees with what goes out.
A template's text is copied
Picking one into a message copies it. Editing the template later cannot rewrite a message somebody already approved and queued.
Variables
The same list in automation steps, announcements, scheduled messages and saved templates.
contact.name: their name from your contactscontact.first_name: the first word of itcontact.last_name: the rest of it, after the first wordcontact.phone_number: the number this is going tocontact.custom.<name>: one of your own fields on their contact, likecontact.custom.checkout_link. See Your own fields.system.ampm,system.24hr: the time it sendssystem.ddmmyyyy,system.mmddyyyy: the date it sends
Names come from the contact in the project doing the sending, or the organization’s contact when the project has none, and never from another project. A contact saved with a first and a last name is stored as one name, so first_name and last_name give them back.
Fetch the live list rather than hard-coding it. It comes from the same table the sender reads, so a documented variable cannot resolve to nothing, and it includes every custom field your contacts carry. An API key works here as well as a signed-in session.
curl https://api.missblue.dev/v1/projects/$PROJECT/templates/variables \
-H "Authorization: Bearer $MISS_BLUE_API_KEY"{
"data": [
{ "name": "contact.first_name", "about": "The first word of their name", "kind": "built_in" },
{ "name": "contact.last_name", "about": "The rest of their name, after the first word", "kind": "built_in" },
{ "name": "contact.custom.checkout_link", "about": "A custom field on their contact", "kind": "custom", "field": "checkout_link" },
{ "name": "contact.custom[\"Order number\"]", "about": "A custom field on their contact", "kind": "custom", "field": "Order number" }
]
}Your own fields
Anything you store on a contact can go in a message: the product they left in their cart, the amount, a checkout link, an appointment time. Write it as {{ contact.custom.product }}. A field whose name has a space or other characters is written with brackets: {{ contact.custom["Checkout link"] }}. Names match exactly, capitals included.
Hi {{ contact.first_name | default: "there" }}, your {{ contact.custom.product }} ({{ contact.custom.amount }}) is still waiting: {{ contact.custom.checkout_link }}- Read when each message sends. Not when the automation was saved or the message was scheduled. Change a field before a follow-up goes and the follow-up says the new value.
- Missing is nil. A contact without the field gets the same treatment as one without a name:
| default:fills it and{% if contact.custom.product %}skips it. The preview counts how many people are missing each one. - Plain text. Values go into the message exactly as stored. Nothing is escaped, so a link with
&in it arrives intact.
Set them when you save the contact by number. Fields you name are set, the others are kept, and a field sent as null, "" or only spaces is removed. Numbers and true/false are stored as text. A contact holds up to 50 fields; names are 1 to 80 characters and values up to 2,000. A name cannot contain }} or %}, or both kinds of quote, because no message could write it. The same custom_data works on each row of an import, and a project’s first save of somebody your organization already has keeps the organization’s fields.
curl -X PUT "https://api.missblue.dev/v1/contacts?project_id=$PROJECT" \
-H "Authorization: Bearer $MISS_BLUE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"handle":"+15555550100","name":"Ada Lovelace","custom_data":{"product":"Blue hoodie","amount":"$48","checkout_link":"https://shop.example/c/91"}}'Or with mb, one --field per value:
mb contact-add --handle +15555550100 --first-name Ada --last-name Lovelace \
--field product="Blue hoodie" --field amount='$48' \
--field checkout_link=https://shop.example/c/91For a sequence that uses them, see From the API, mb or an agent in Automations, and the Campaigns API reference.
Fallbacks
A missing value is nil, not an empty string — which is why | default: replaces it and {% if %} takes the branch you meant. An empty string is truthy in Liquid and would take the branch written for people who have a name.
Hi {{ contact.name | default: "there" }}, see you Tuesday.
{% if contact.first_name %}Thanks, {{ contact.first_name }}!{% else %}Thanks!{% endif %}Preview before you send
Pass the handles you are actually sending to and the response counts how often each variable is empty across them. “38 of 412 have no name” is a decision; “may be empty” is not.
curl -X POST https://api.missblue.dev/v1/projects/$PROJECT/templates/preview \
-H "Authorization: Bearer $MISS_BLUE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Hi {{ contact.name }}!","handles":["+13055550142"]}'
{
"text": "Hi Dana!",
"error": null,
"variables": ["contact.name"],
"unresolved": [],
"missing": [],
"counted": 1
}A template that will not parse comes back with text: null and one line in error. Saving one is refused outright, because the send path has no good option: printing the source is a public failure and refusing is a message nobody receives for a reason they discover later.
Save one
curl -X POST https://api.missblue.dev/v1/projects/$PROJECT/templates \
-H "Authorization: Bearer $MISS_BLUE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Appointment reminder","body":"Hi {{ contact.name | default: \"there\" }}, see you tomorrow."}'