Using Handlebars

Twilio SendGrid Dynamic Transactional Templates and Marketing Campaigns designs support the Handlebars templating language to render the Personalizations you send via the API and the Substitution Tags stored on your Marketing Campaigns contacts.

Handlebars syntax allows you to personalize the messages in your templates by inserting customers' names and other data to make an email relevant to each individual recipient. For example, if you have a customer's name stored in a JSON property called "name", you can insert the property's value into a template by adding {{ name }} wherever you want the customer's name to appear.

Handlebars syntax allows all of this dynamic templating to occur outside of your code base, meaning changes are done quickly in the template with no update to a code base required.

If you prefer to use your own templating system, you can still insert dynamic data using Substitution Tags.

You can manage your templates programmatically with our Mail Send with Dynamic Transactional Templates API.

The Handlebars language provides many features in addition to basic variable replacement, including iterations (loops) and conditional statements. Our templates and designs support most but not all of this Handlebars functionality. Currently, dynamic templates support the following helpers:

For a full helper reference, see the Handlebars reference on this page.

Use cases

The following use case examples come from the dynamic-template section of our email templates GitHub repo. Each example links to files you can explore on GitHub. You can also work with these templates by uploading them using the Code Editor available in Dynamic Transactional Templates and the Marketing Campaigns Design Library.

The following use cases are listed with the Handlebars helpers used to build them.

Receipt

This example receipt template uses the following helpers:

Password reset

This example transactional template uses the following helpers:

Multiple languages

This is an example template that lets you have content in multiple languages, and it uses the following helpers:

Newsletter

This example newsletter template uses the following helpers:

This is an example template that is advertising items on sale, and it uses the following helpers:

Handlebars reference

The following reference provides sample code blocks for each helper, including HTML email snippets and JSON test data. The code examples are shown in three tabs. The first tab, Handlebars, shows the Handlebars tag. The second tab, JSON, shows example data that would be used to populate the Handlebars tag. The third tab, HTML, shows the final output that the Handlebars tag will be rendered to in your email. You can click each tab to switch between the code samples.

Substitution

Twilio SendGrid templates support the following substitutions:

Basic replacement

<!-- Template -->
<p>Hello {{ firstName }}</p>
// Test data
{ "firstName": "Ben" }
<!-- Resulting HTML !-->
<p>Hello Ben</p>

Deep object replacement

<!-- Template -->
<p>Hello {{user.profile.firstName}}</p>
// Test data
{
  "user": {
    "profile": {
      "firstName": "Ben"
    }
  }
}
<!-- Resulting HTML -->
<p>Hello Ben</p>

Object failure

<!-- Template -->
<p>Hello {{user.profile.firstName}}</p>
// Test data
{
  "user": {
    "orderHistory": [
      {
        "date": "2/1/2018",
        "item": "shoes"
      },
      {
        "date": "1/4/2017",
        "item": "hat"
      }
    ]
  }
}
<!-- Resulting HTML -->
<p>Hello</p>

Replacement with HTML

If you include the characters ', " or & in a subject line replacement be sure to use three brackets like below.

<!-- Template -->
<!-- Per Handlebars' documentation: If you don't want Handlebars to escape a value, use the "triple-stash", {{{ -->
<p>Hello {{{firstName}}}</p>
// Test data
{ "firstName": "<strong>Ben</strong>" }
<!-- Resulting HTML -->
<p>Hello <strong>Ben</strong></p>

formatDate

The formatDate helper takes a time in either epoch or ISO8601 format and converts it to a format you specify using the tokens in the following table. If you send a date field without converting it, it will be displayed in ISO8601 format with the full timestamp (e.g., 2020-01-01T23:00:00.000Z). The following example display results are for Tuesday, January 1st, 2020 3:00:00PM Pacific Standard Time.

Token Displayed Result
YYYY 2020
YY 20
MMMM January
MMM Jan
MM 01
M 1
DD 01
D 1
dddd Tuesday
ddd Tue
hh 03
h 3
HH 00
H 00
mm 00
m 0
ss 00
s 0
A PM
ZZ -0800
Z -08:00

<!-- Template without timezone offset -->
<p>Join us {{formatDate timeStamp dateFormat}}</p>

<!-- Template with timezone offset -->
<p>Join us {{formatDate timeStamp dateFormat timezoneOffset}}</p>
// Test data
{
  "timeStamp": "2020-01-01T23:00:00.000Z",
  "dateFormat": "MMMM:DD:HH:mm:ss",
  "timezoneOffset": "-0800"
}
<!-- Resulting HTML without timezone-->
<p>Join us January 01, 2020 11:00:00PM</p>

<!-- Resulting HTML with timezone-->
<p>Join us January 01, 2020 3:00:00PM</p>

Insert

<!-- Insert with a default value -->
<p>Hello {{insert name "default=Customer"}}! Thank you for contacting us about {{insert businessName "your business"}}.</p>
// Test data with all values
{
   "name": "Ben",
   "businessName": "Twilio SendGrid"
}

// Test data with missing value
{
  "name": "Ben"
}
<!-- Resulting HTML with all values -->
<p>Hello Ben! Thank you for contacting us about Twilio SendGrid.</p>

<!-- Resulting HTML with missing value and a default-->
<p>Hello Ben! Thank you for contacting us about your business.</p>

Conditional statements

Twilio SendGrid templates support the following conditionals:

Basic If, Else, Else If

<!-- Template -->
{{#if user.profile.male}}
   <p>Dear Sir</p>
{{else if user.profile.female}}
   <p>Dear Madame</p>
{{else}}
   <p>Dear Customer</p>
{{/if}}
// Test data one
{
   "user":{
      "profile":{
         "male":true
      }
   }
}

// Test data two
{
   "user":{
      "profile":{
         "female":true
      }
   }
}

// Test data three
{
   "user":{
      "profile":{

      }
   }
}
<!-- Resulting HTML from test data one -->
<p>Dear Sir</p>

<!-- Resulting HTML from test data two -->
<p>Dear Madame</p>

<!-- Resulting HTML from test data three -->
<p>Dear Customer</p>

If with a root

<!-- Template -->
{{#if user.suspended}}
   <p>Warning! Your account is suspended, please call: {{@root.supportPhone}}</p>
{{/if}}
// Test data
{
  "user": {
    "suspended": true
  },
  "supportPhone": "1-800-555-5555"
}
<!-- Resulting HTML -->
<p>Warning! Your account is suspended, please call: 1-800-555-5555</p>

Unless

<!-- Template -->
{{#unless user.active}}
   <p>Warning! Your account is suspended, please call: {{@root.supportPhone}}</p>
{{/unless}}
// Test data
{
  "user": {
    "active": false
  },
  "supportPhone": "1-800-555-5555"
}
<!-- Resulting HTML -->
<p>Warning! Your account is suspended, please call: 1800-555-5555</p>

greaterThan

Basic greaterThan

<!-- Template -->
<p>
Hello Ben!
{{#greaterThan scoreOne scoreTwo}}
    Congratulations, you have the high score today!
{{/greaterThan}}
 Thanks for playing.
</p>
// Test data one
{
  "scoreOne": 100,
  "scoreTwo": 78
}

// Test data two
{
  "scoreOne": 55,
  "scoreTwo": 78
}
<!-- Resulting HTML from test data one-->
<p>
  Hello Ben! Congratulations, you have the high score today! Thanks for playing.
</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! Thanks for playing.</p>

greaterThan with else

<!-- Template -->
<p>
Hello Ben!
{{#greaterThan scoreOne scoreTwo}}
    Congratulations, you have the high score today!
{{else}}
    You were close, but you didn't get the high score today.
{{/greaterThan}}
 Thanks for playing.
</p>
// Test data one
{
  "scoreOne": 100,
  "scoreTwo": 78
}

// Test data two
{
  "scoreOne": 55,
  "scoreTwo": 78
}
<!-- Resulting HTML from test data one-->
<p>
  Hello Ben! Congratulations, you have the high score today! Thanks for playing.
</p>

<!-- Resulting HTML from test data two-->
<p>
  Hello Ben! You were close, but you didn't get the high score today. Thanks for
  playing.
</p>

lessThan

Basic lessThan

<!-- Template -->
<p>
Hello Ben!
{{#lessThan scoreOne scoreTwo}}
    You were close, but you didn't get the high score today.
{{/lessThan}}
 Thanks for playing.
</p>
// Test data one
{
  "scoreOne": 55,
  "scoreTwo": 78
}

// Test data two
{
  "scoreOne": 100,
  "scoreTwo": 78
}
<!-- Resulting HTML from test data one-->
<p>
  Hello Ben! You were close, but you didn't get the high score today. Thanks for
  playing.
</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! Thanks for playing.</p>

lessThan with else

<!-- Template -->
<p>
Hello Ben!
{{#lessThan scoreOne scoreTwo}}
    You were close, but you didn't get the high score today.
{{else}}
    Congratulations, you have the high score today!
{{/lessThan}}
 Thanks for playing.
</p>
// Test data one
{
  "scoreOne": 55,
  "scoreTwo": 78
}

// Test data two
{
  "scoreOne": 100,
  "scoreTwo": 78
}
<!-- Resulting HTML from test data one-->
<p>
  Hello Ben! You were close, but you didn't get the high score today. Thanks for
  playing.
</p>

<!-- Resulting HTML from test data two-->
<p>
  Hello Ben! Congratulations, you have the high score today! Thanks for playing.
</p>

Equals

The equals comparison can check for equality between two values of the same data type. The equals helper will also attempt to coerce data types to make a comparison of values independent of their data type. For example, {{#equals 3 "3"}} will evaluate to true.

Please be aware that the editor's Preview page will not properly render the results of a comparison between coerced values. You will see proper comparisons between coerced values only in a delivered message.

When checking for truthiness, be aware that empty strings, zero integers, and zero floating point numbers evaluate to false. Non-empty strings, non-zero integers, and non-zero floating point numbers, including negative numbers, evaluate to true.

Basic equals

<!-- Template -->
<p>
Hello Ben!
{{#equals customerCode winningCode}}
    You have a winning code.
{{/equals}}
 Thanks for playing.
</p>
// Test data one
{
  "customerCode": 289199,
  "winningCode": 289199
}

// Test data two
{
  "customerCode": 167320,
  "winningCode": 289199
}
<!-- Resulting HTML from test data one-->
<p>Hello Ben! You have a winning code. Thanks for playing.</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! Thanks for playing.</p>

Equals with else

<!-- Template -->
<p>
Hello Ben!
{{#equals customerCode winningCode}}
    You have a winning code.
{{else}}
    You do not have a winning code.
{{/equals}}
 Thanks for playing.
</p>
// Test data one
{
  "customerCode": 289199,
  "winningCode": 289199
}

// Test data two
{
  "customerCode": 167320,
  "winningCode": 289199
}
<!-- Resulting HTML from test data one-->
<p>Hello Ben! You have a winning code. Thanks for playing.</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! You do not have a winning code. Thanks for playing.</p>

notEquals

The notEquals comparison can check for equality between two values of the same data type. The notEquals helper will also attempt to coerce data types to make a comparison of values independent of their data type. For example, {{#equals 3 "3"}} will return false.

When checking for truthiness, be aware that empty strings, zero integers, and zero floating point numbers evaluate to false. Non-empty strings, non-zero integers, and non-zero floating point numbers, including negative numbers, evaluate to true.

Basic notEquals

<!-- Template -->
<p>
Hello Ben!
{{#notEquals customerCode winningCode}}
    You have a winning code.
{{/notEquals}}
 Thanks for playing.
</p>
// Test data one
{
  "customerCode": 289199,
  "winningCode": 289199
}

// Test data two
{
  "customerCode": 167320,
  "winningCode": 289199
}
<!-- Resulting HTML from test data one-->
<p>Hello Ben! You have a winning code. Thanks for playing.</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! Thanks for playing.</p>

notEquals with else

<!-- Template -->
<p>
Hello Ben!
{{#notEquals customerCode winningCode}}
    You have a winning code.
{{else}}
    You do not have a winning code.
{{/notEquals}}
 Thanks for playing.
</p>
// Test data one
{
  "customerCode": 289199,
  "winningCode": 289199
}

// Test data two
{
  "customerCode": 167320,
  "winningCode": 289199
}
<!-- Resulting HTML from test data one-->
<p>Hello Ben! You have a winning code. Thanks for playing.</p>

<!-- Resulting HTML from test data two-->
<p>Hello Ben! You do not have a winning code. Thanks for playing.</p>

And

When checking for truthiness, be aware that empty strings, zero integers, and zero floating point numbers evaluate to false. Non-empty strings, non-zero integers, and non-zero floating point numbers, including negative numbers, evaluate to true.

And without else

<!-- Template -->
<p>
Hello Ben!
{{#and favoriteFood favoriteDrink}}
   Thank you for letting us know your dining preferences.
{{/and}}.
 We look forward to sending you more delicious recipes.</p>
// Test data one
{
  "favoriteFood": "Pasta",
  "favoriteDrink": ""
}

// Test data two
{
  "favoriteFood": "Pasta",
  "favoriteDrink": "Coffee"
}
<!-- Resulting HTML from test data one -->
<p>Hi Ben! We look forward to sending you more delicious recipes.</p>

<!-- Resulting HTML from test data two -->
<p>
  Hi Ben! Thank you for letting us know your dining preferences. We look forward
  to sending you more delicious recipes.
</p>

And with else

<!-- Template -->
<p>
Hello Ben!
{{#and favoriteFood favoriteDrink}}
   Thank you for letting us know your dining preferences.
{{else}}
   If you finish filling out your dining preferences survey, we can deliver you recipes we think you'll be most interested in.
{{/and}}.
 We look forward to sending you more delicious recipes.</p>
// Test data one
{
  "favoriteFood": "Pasta",
  "favoriteDrink": ""
}

// Test data two
{
  "favoriteFood": "Pasta",
  "favoriteDrink": "Coffee"
}
<!-- Resulting HTML from test data one -->
<p>
  Hi Ben! If you finish filling out your dining preferences survey, we can
  deliver you recipes we think you'll be most interested in. We look forward to
  sending you more delicious recipes.
</p>

<!-- Resulting HTML from test data two -->
<p>
  Hi Ben! Thank you for letting us know your dining preferences. We look forward
  to sending you more delicious recipes.
</p>

Or

When checking for truthiness, be aware that empty strings, zero integers, and zero floating point numbers evaluate to false. Non-empty strings, non-zero integers, and non-zero floating point numbers, including negative numbers, evaluate to true.

Basic or

<!-- Template -->
<p>
Hello Ben!
{{#or isRunner isCyclist}}
   We think you might enjoy a map of trails in your area.
{{/or}}.
 Have a great day.
</p>
// Test data one
{
  "isRunner": true,
  "isCyclist": false
}

// Test data two
{
  "isRunner": false,
  "isCyclist": false
}
// Test data three
{
  "isRunner": false,
  "isCyclist": true
}
<!-- Resulting HTML from test data one -->
<p>
  Hi Ben! We think you might enjoy a map of trails in your area. You can find
  the map attached to this email. Have a great day.
</p>

<!-- Resulting HTML from test data two -->
<p>Hi Ben! Have a great day.</p>

<!-- Resulting HTML from test data three -->
<p>
  Hi Ben! We think you might enjoy a map of trails in your area. You can find
  the map attached to this email. Have a great day.
</p>

Or with else

<!-- Template -->
<p>
Hello Ben!
{{#or isRunner isCyclist}}
   We think you might enjoy a map of trails in your area. You can find the map attached to this email.
{{else}}
   We'd love to know more about the outdoor activities you enjoy. The survey linked below will take only a minute to fill out.
{{/or}}.
 Have a great day.
</p>
// Test data one
{
  "isRunner": true,
  "isCyclist": false
}

// Test data two
{
  "isRunner": false,
  "isCyclist": false
}
// Test data three
{
  "isRunner": false,
  "isCyclist": true
}
<!-- Resulting HTML from test data one -->
<p>
  Hi Ben! We think you might enjoy a map of trails in your area. You can find
  the map attached to this email. Have a great day.
</p>

<!-- Resulting HTML from test data two -->
<p>
  Hi Ben! We'd love to know more about the outdoor activities you enjoy. The
  survey linked below will take only a minute to fill out. Have a great day.
</p>

<!-- Resulting HTML from test data three -->
<p>
  Hi Ben! We think you might enjoy a map of trails in your area. You can find
  the map attached to this email. Have a great day.
</p>

Length

The length helper will return the number of characters in a given string or array. For non-string and non-array values, length will return 0. Length can be useful in combination with other helpers as shown with greaterThan in the following example.

<!-- Templates -->
<p>
Hello Ben!
{{#greaterThan 0 length cartItems}}
 It looks like you still have some items in your shopping cart. Sign back in to continue checking out at any time.
{{else}}
 Thanks for browsing our site. We hope you'll come back soon.
{{/greaterThan}}
</p>
// Test data one
{
  "cartItems": ["raft", "water bottle", "sleeping bag"]
}

// Test data two
{
  "cartItems": []
}
<!-- Resulting HTML with test data one-->
<p>
  Hello Ben! It looks like you still have some items in your shopping cart. Sign
  back in to continue checking out at any time.
</p>

<!-- Resulting HTML with test data two-->
<p>Hello Ben! Thanks for browsing our site. We hope you'll come back soon.</p>

Iterations

You can loop or iterate over data using the {{#each }} helper function to build lists and perform other useful templating actions.

Basic Iterator with each

<!-- Template -->
<ol>
  {{#each user.orderHistory}}
   <li>You ordered: {{this.item}} on: {{this.date}}</li>
  {{/each}}
</ol>
// Test data
{
  "user": {
    "orderHistory": [
      {
        "date": "2/1/2018",
        "item": "shoes"
      },
      {
        "date": "1/4/2017",
        "item": "hat"
      }
    ]
  }
}
<!-- Resulting HTML -->
<ol>
  <li>You ordered: shoes on: 2/1/2018</li>
  <li>You ordered: hat on: 1/42017</li>
</ol>

Combined examples

The following examples show you how to combine multiple Handlebars functions to create a truly dynamic template.

Dynamic content creation

<!-- Template -->
{{#each user.story}}
   {{#if this.male}}
      <p>{{this.date}}</p>
   {{else if this.female}}
      <p>{{this.item}}</p>
   {{/if}}
{{/each}}
// Test data
{
  "user": {
    "story": [
      {
        "male": true,
        "date": "2/1/2018",
        "item": "shoes"
      },
      {
        "male": true,
        "date": "1/4/2017",
        "item": "hat"
      },
      {
        "female": true,
        "date": "1/1/2016",
        "item": "shirt"
      }
    ]
  }
}
<!-- Resulting HTML -->
<p>2/1/2018</p>
<p>1/4/2017</p>
<p>shirt</p>

Dynamic content creation with dynamic parts 1

<!-- Template -->
{{#each user.story}}
   {{#if this.male}}
      {{#if this.date}}
         <p>{{this.date}}</p>
      {{/if}}
      {{#if this.item}}
         <p>{{this.item}}</p>
      {{/if}}
   {{else if this.female}}
      {{#if this.date}}
         <p>{{this.date}}</p>
      {{/if}}
      {{#if this.item}}
         <p>{{this.item}}</p>
      {{/if}}
   {{/if}}
{{/each}}
// Test data
{
  "user": {
    "story": [
      {
        "male": true,
        "date": "2/1/2018",
        "item": "shoes"
      },
      {
        "male": true,
        "date": "1/4/2017"
      },
      {
        "female": true,
        "item": "shirt"
      }
    ]
  }
}
<!-- Resulting HTML -->
<p>2/1/2018</p>
<p>shoes</p>
<p>1/4/2017</p>
<p>shirt</p>

Dynamic content creation with dynamic parts 2

<!-- Template -->
{{#if people}}
   <p>People:</p>
   {{#each people}}
      <p>{{this.name}}</p>
   {{/each}}
{{/if}}
// Test data
{
  "people": [{ "name": "Bob" }, { "name": "Sally" }]
}
<!-- Resulting HTML -->
<p>People:</p>
<p>Bob</p>
<p>Sally</p>

Additional Resources

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the SendGrid tag on Stack Overflow.