Template language
Templates use a Handlebars-style syntax. Placeholders in mjml, content, and subject are filled from the data object when you send an email.
{{variable}}, literals, or, {{#if}}, and {{#each}} work in both MJML and HTML content templates. :if, :each, and unquoted attributes like color={{color}} are MJML-only — they are handled by Mailcast’s MJML compiler.
Variables
{{name}} looks up name in data. Dotted paths walk nested maps: {{user.email}} reads data.user.email.
Hello {{name}}
With name set to Andrew in data, this renders Hello Andrew.
Variables can access nested structures using dot notation. For example, {{user.email}} reads data.user.email.
Missing keys and subkeys are ignored.
{{account.user.name}} will return nothing when account or user or name is missing.
Literals
{{'red'}} and {{"red"}} always render red. A mustache may contain only a literal but they are usually used with or to provide a fallback.
Quotes inside a literal do not start another template. {{name or "{{name or \"hello world\"}}"}} is name when present, otherwise the string {{name or "hello world"}}.
Defaults
{{color or 'red'}} uses color when it is present, otherwise the next value. You can chain: {{color or fallback or 'red'}}.
null, false, and "" are considered missing and will trigger the or value. Any other value is considered present and will be displayed.
Attributes
Mustaches work inside quoted HTML attribute values in both MJML and content. Use the opposite quote from the attribute so the value does not close early:
<div style="color:{{color or 'red'}}">
<a href="{{url or 'https://example.com'}}">
<mj-text color="{{color or 'red'}}">Hello</mj-text>
In MJML only, you can also write an unquoted attribute whose whole value is a mustache. The compiler keeps it so it can be substituted when you send:
<mj-text color={{color}}>Hello</mj-text>
<mj-button href={{url}}>Continue</mj-button>
<mj-image src={{badge_url}} />
Unquoted color={{color}} is not valid HTML, so it does not work in content templates. MJML attributes that contain {{ are not type-checked at compile time.
Conditionals
{{#if name}}…{{/if}} includes the block when name is present. {{else}} is the fallback.
Hello {{#if name}}{{name}}{{else}}there{{/if}}
Conditions also accept not, and, or, and comparisons (==, !=, >, >=, <, <=):
{{#if age >= 13 and age <= 19}}teenager{{/if}}
{{#if not active}}Inactive{{/if}}
:if is MJML-only. The value is an expression: :if={{show}} compiles to {{#if show}}. There is no :else; use {{#if}} / {{else}} in the text when you need a fallback. In content templates, write {{#if}} directly.
<mj-text :if={{show}}>Welcome</mj-text>
This is equivalent to:
{{#if show}}
<mj-text>
Welcome
</mj-text>
{{/if}}
Loops
{{#each users as |user|}}…{{/each}} repeats the block for each item. Inside the block, {{user}} is the item and {{user.name}} is a field.
A missing or empty list renders nothing.
:each is MJML-only. The value is an expression: :each={{users as |user|}} compiles to {{#each users as |user|}}. In content templates, write {{#each}} directly:
<mj-text :each={{users as |user|}}>Hello {{user.name}}</mj-text>
This is equivalent to:
{{#each users as |user|}}
<mj-text>
Hello {{user.name}}
</mj-text>
{{/each}}
:if and :each can be combined. :each is wrapped inside :if.
Full example
Template
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text color="{{accent or 'red'}}">
Hello {{#if user.name}}{{user.name}}{{else}}there{{/if}}
</mj-text>
<mj-text :if={{promo}}>{{promo}}</mj-text>
<mj-text :each={{items as |item|}}>{{item.name}}</mj-text>
<mj-text>
{{#each items as |item|}}
{{item.name}} — {{item.price}}
{{/each}}
</mj-text>
<mj-button href="{{url or 'https://example.com'}}">
Continue
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Data
{
"user": { "name": "Andrew" },
"promo": "Free shipping",
"items": [
{ "name": "Apples", "price": "$4" },
{ "name": "Oranges", "price": "$3" }
],
"url": "https://mailcast.io"
}
accent is omitted, so the text color falls back to red.
Result
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text color="red">
Hello Andrew
</mj-text>
<mj-text>Free shipping</mj-text>
<mj-text>Apples</mj-text>
<mj-text>Oranges</mj-text>
<mj-text>
Apples — $4
Oranges — $3
</mj-text>
<mj-button href="https://mailcast.io">
Continue
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>