Creating AWS email templates with Handlebars.js and MJML

In this series, I'll share how we have developed elmah.io's email templates currently sent out using Amazon Web Services (AWS). This first post will introduce template development using MJML and Handlebars.js. In the following posts, I'll explain the process of building them with Gulp on Azure DevOps and deploying them to AWS.

Creating AWS email templates with Handlebars.js and MJML

So, let's kick off this blog post with a bit of chit-chat around email templates. If you have sent out emails from C# (or another language), you may have just generated either clear-text or HTML and have SmtpClient or MailKit sent the email using your favorite SMTP server. There are both up and downsides to a simple approach like this. The main downsides are that HTML is typically produced in C# and you'd have to send the entire markup to the SMTP server on every email. Various email services have solved this issue by introducing email templates.

An email template is a pre-defined HTML (or clear text) document with different ways of embedding dynamic content. We have used multiple email solutions in elmah.io during the days, but we settled on AWS for now. AWS supports email templates with embedded Handlebars.js templates in them. In case you are unfamiliar with Handlebars.js, it's a templating language that brings dynamic content into many file types, where HTML is just one possibility. A simple example of HTML including Handlebars.js code can look like this:

<html>
  <body>
    <p>Hello {{firstname}}</p>
    <p>Here are some movie recommendations for you:</p>
    <ul>
      {{#each movies}}
      <li>{{this}}</li>
      {{/each}}
    </ul>
  </body>
</html>

When processed through Handlebars.js, you'd want to provide it with an object that will be used to fill in the firstname and movies variables. As shown in the example, not only simple text replacement is supported, but also iteration using the #each expression. The object to satisfy the example above could be:

{
    firstname: "Thomas",
    movies: ["Star Wars", "The Big Lebowski", "The Godfather"]
}

Rather than generating the entire HTML locally and asking AWS to send out the generated content as an email, using email templates is simpler. You start by deploying and naming the email template including the Handlebars.js expressions to AWS (as shown in a future post). Then, when needing to send out this email, you ask AWS to send out the template by referencing its name and providing it with the input object as part of the request. This way, you forward the work of generating the HTML to AWS, reducing the work you need to do on your side and limiting the number of bytes sent over the wire. The last part is key if you like us are communicating from Azure (or another cloud not AWS) to AWS since you are charged for outgoing traffic.

For the second part of this post let's talk MJML. A common issue when generating emails with HTML is that email clients may and will display the same markup in different ways. You may have users with old Outlook browsers or similar, not supporting modern HTML. Writing HTML that works in all browsers is hard and time-consuming since you'd need to spend time testing the output in a range of browsers. There are services to help you automate this task out there, but IMO there's a better approach: MJML.

MJML is a markup language developed by Mailjet. The language itself is written in XML and works by translating MJML code to HTML which works in most browsers. Let's re-write the example above in MJML:

<mjml>
  <mj-body>
    <mj-text>Hello {{firstname}}</mj-text>
    <mj-text>Here are some movie recommendations for you:</mj-text>
    <mj-text>
      <ul>
        {{#each movies}}
        <li>{{this}}</li>
        {{/each}}
      </ul>
    </mj-text>
  </mj-body>
</mjml>
While looking similar in structure to the HTML sample, various elements have been switched to using mj-* variants. For this post, I'll generate HTML output using the online MJML editor, but in the next post, we'll generate it automatically. When inspecting the generated HTML, you will notice that it is much more complex than the HTML sample above:
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">

<head>
  <title>
  </title>
  <!--[if !mso]><!-->
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <!--<![endif]-->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style type="text/css">
    #outlook a {
      padding: 0;
    }

    body {
      margin: 0;
      padding: 0;
      -webkit-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
    }

    table,
    td {
      border-collapse: collapse;
      mso-table-lspace: 0pt;
      mso-table-rspace: 0pt;
    }

    img {
      border: 0;
      height: auto;
      line-height: 100%;
      outline: none;
      text-decoration: none;
      -ms-interpolation-mode: bicubic;
    }

    p {
      display: block;
      margin: 13px 0;
    }
  </style>
  <!--[if mso]>
        <noscript>
        <xml>
        <o:OfficeDocumentSettings>
          <o:AllowPNG/>
          <o:PixelsPerInch>96</o:PixelsPerInch>
        </o:OfficeDocumentSettings>
        </xml>
        </noscript>
        <![endif]-->
  <!--[if lte mso 11]>
        <style type="text/css">
          .mj-outlook-group-fix { width:100% !important; }
        </style>
        <![endif]-->
  <!--[if !mso]><!-->
  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
  <style type="text/css">
    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
  </style>
  <!--<![endif]-->
  <style type="text/css">
  </style>
</head>

<body style="word-spacing:normal;">
  <div style="">
    <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">Hello {{firstname}}</div>
    <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">Here are some movie recommendations for you:</div>
    <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
      <ul>
        {{#each movie}}
          <li>{{this}}</li>
        {{/each}}
      </ul>
    </div>
  </div>
</body>

</html>

There's suddenly a lot of content in there, to make sure the email looks good in various email clients, based on different browser engines and so on.

That's it for this post. In the next post, I'll show you how to automatically build an MJML template with Gulp and prepare it for upload to AWS. For more information about sending emails from C# check out How to send emails from C#/.NET - The definitive tutorial.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website