Grow with us: Join Postmark's new referral partner program and start earning
x

Template Syntax

Postmark Templates use a very simple, yet very powerful, language called Mustachio. Mustachio has its roots with Mustache, but includes a few advanced features that make it great for authoring plain text and html emails.

Here's everything you need to know about the language.

Variable Interpolation#

“Variable Interpolation” is the heart and soul of any templating language.

If you have a template model like this (represented as JSON):

{
  "person": {
    "first_name": "Andrew"
  }
}

And this template:

Hello {{ person.first_name }}

Mustachio will produce this content when you combine the two:

Hello Andrew

If you've used Mustache.js, this should be very familiar. But Mustachio has a few more capabilities that make writing templates a breeze.

Scoping#

Sometimes we'll have more complex models, and so scoping to a particular property will make the markup easier to read:

{{#person}}
  Hello {{first_name}}
{{/person}}

Notice that if we're building a section of the template that uses multiple properties from “person”, we don't need to keep repeating “person.” with each variable interpolation.

Collection Handling#

Let's say we have this more complicated model:

{
  "company_name": "ACME Rockets, Inc.",
  "employees": [
    {
      "name": "Wile E. Coyote"
    },
    {
      "name": "Road Runner"
    }
  ]
}

And this template:

{{ company_name }} Employees:
<ul>
{{#each employees}}
  <li>{{ name }}</li>
{{/each}}
<ul>

Mustachio will combine these and produce the following HTML:

ACME Rockets, Inc. Employees
<ul>
  <li>Wile E. Coyote</li>
  <li>Road Runner</li>
</ul>

Advanced Interpolation#

If we've scoped our template to a property of a model, we may want to “reach up” to a property in the outer scope. For example, let's say we wanted to change the content from the Collection Handling example above to look like this:

<ul>
  <li>Wile E. Coyote works for: ACME Rockets, Inc.</li>
  <li>Road Runner works for: ACME Rockets, Inc.</li>
</ul>

We can use a special interpolation syntax to do this without needing to repeat the values in our template model:

{{ company_name }} Employees:
<ul>
{{#each employees}}
  <li>{{name}} works for: {{../../company_name}}</li>
{{/each}}
</ul>

Note the ../ in the template, which just means “go up one level” in my template model and look for the property name that follows. You can go up as many levels in your model as needed by including ../ multiple times at the start of your {{ ... }} section.

Advanced Value Handling#

Mustachio is permissive about missing values in your models. If you scope to a property that does not exist, Mustachio will skip that section. In this way, you can build templates that do not require if/else logic.

To show this, let's use the following model:

{
  "company_name": "ACME Rockets, Inc.",
  "employees": [
    {
      "name": "Wile E. Coyote",
      "position": "Quality Assurance Tester"
    },
    {
      "name": "Road Runner"
    }
  ]
}

And we update the template:

<ul>
  {{#each employees}}
    <li>
      {{name}} works for: {{../../company_name}}
      {{#position}}as a {{.}}{{/position}}
    </li>
  {{/each}}
</ul>

When we use Mustachio we will produce the following HTML:

<ul>
  <li>Wile E. Coyote works for: ACME Rockets, Inc. as a Quality Assurance Tester</li>
  <li>Road Runner works for: ACME Rockets, Inc.</li>
</ul>

Note that because Road Runner doesn't have a “position” property, “as a...“ is omitted from the output. Also note that we can use . in the Variable Interpolation to reference the model object in the {{#position}} scoped block.

Creating a list of variables within a list of variables#

Say for example, that you need to list some items, each of which, includes a list of items. For example, if you’re selling fruits and veggies, you’ll need to put them in a list, and also list the available fruits and the available veggies in your message, for example, you want to show:

For tonight's dinner we have:

Fruits!

  • Apple
  • Banana
  • orange

Veggies!

  • Brocolli
  • Cucumber.

Your template would have:

<body>
<strong>For tonight's dinner we have:</strong>

{{#fruits}}

<p>Fruits!</p>

<ul>

{{#each fruitVariety}}
<li>{{fruitVarietyType}}</li>
{{/each}}

</ul>

{{/fruits}}

{{#veggies}}

<p>Veggies!</p>

<ul>
{{#each veggieVariety}}
<li>{{veggieVarietyType}}</li>
{{/each}}

</ul>

{{/veggies}}

</body>

In your Template Model, you can then pass the following to have two unordered lists appear:

{
"fruits": {
"fruitVariety": [
{
"fruitVarietyType": "apple"
},
{
"fruitVarietyType": "banana"
},
{
"fruitVarietyType": "orange"
}
]
},
"veggies": {
"veggieVariety": [
{
"veggieVarietyType": "broccoli"
},
{
"veggieVarietyType": "cucumbers"
}
]
}
}

{
"fruits": {
"fruitVariety": [
{
"fruitVarietyType": "apple"
},
{
"fruitVarietyType": "banana"
},
{
"fruitVarietyType": "orange"
}
]
},
"veggies": {
"veggieVariety": [
{
"veggieVarietyType": "broccoli"
},
{
"veggieVarietyType": "cucumbers"
}
]
}
}

Inverted Groups (or, how to make Placeholders)#

Mustachio will skip anything in a template that reference a value that is null, false, or empty (such as an array that has no elements). However, sometimes it's useful to include content specifically when a value is absent from the model, and, of course, Mustachio supports this using “Inverted Groups”:

<ul>
{{#each employees}}
  <li>
    {{name}} works for: {{../../company_name}}
    {{#position}} as a {{.}}{{/position}}
    {{^ years_employed}}since who knows when?{{/years_employed}}
  </li>
{{/each}}
</ul>

In the above example, if years_employed is not specified in our template model, and so therefore, since who knows when? will be rendered.

Inverted Groups are a powerful way to provide placeholder text when information is not available, or not applicable.

Content Safety#

Normally, the information you want to include in a template will be simple scalar values, these values typically lack html markup, and are safe to render to browsers as-is. However, if you accept content from untrusted sources that you wish to embed, it's possible that this content could contain unsafe values that could lead to issues when rendered and served in a browser. This is a common “cross-site scripting” attack, and Mustachio is safe by default:

Let's take this model:

{
  "user_submitted_content": "<script>$.sendUserPasswordToUntrustedWebsite();</script>"
}

And this template:

<div class="user_comment">{{user_submitted_content}}</div>

Mustachio automatically HTML encodes the values that it interpolates, so that, while producing “ugly” content, the output is rendered harmless:

<div class="user_comment"><script>$.sendUserPasswordToUntrustedWebsite();</script></div>

If you need to opt-out of this behavior, Mustachio provides two syntaxes to accomplish this:

{{{ user_submitted_content }}}

and

{{& user_submitted_content }}

Remember that not escaping content creates a security risk if you'll be presenting the template's output in a browser.

Layout Content Placeholder#

Layouts let Templates share share common elements like CSS, headers, and footers. Within a Layout, to specify where to insert the Template in the Layout, use the content placeholder:

{{{ @content }}}

When previewing a Layout, the Template content shows as [Template @content goes here].

To see how a Template looks with a Layout, head over to the Template and choose Preview.

Every message sent through a Broadcast Message Stream in Postmark is required to have an unsubscribe link. If you're sending with Postmark's Templates you can add an unsubscribe placeholder with its HTML:

{{{ pm:unsubscribe }}}

By default, the unsubscribe link will have a text of “Unsubscribe”. To change the default text, treat the unsubscribe placeholder as a hyperlink:

<a href="{{{ pm:unsubscribe }}}">Unsubscribe from this list</a>

Unsubscribe links included in Templates will also work for messages sent through Transactional Message Streams, though they are not required. Read our helpful guide on how to add unsubscribe links to your templates.

Last updated January 2nd, 2024

Still need some help?

Our customer success team has your back!