Building Responsive Emails That Just Work

Paperless Post’s quest to build awesome, beautiful emails — and making sure they look good in every inbox

Anthony Jiménez
Life at Paperless Post
7 min readJul 24, 2019

--

If you are familiar with front-end technologies, you are likely accustomed to working in a browser and have seen cross-browser compatibility improve over the years. You might be using the sleekest JavaScript frameworks and bleeding edge ES features, but if you need to send rich formatted emails, they aren’t going to help you. For us at Paperless Post, this is a challenge close to our hearts as we send millions of emails a day — and since our first touchpoint with many of our users is via an invitation email, we really need to impress them.

Depending on your product, you might be able to get away with building plain text emails if you only use email for transactional tasks such as email verification and password reset. While these technologies have improved in many ways, you may be surprised to learn that generating beautiful emails is still very difficult due to a limited subset of archaic HTML standards. Some clients have poor support for standard technologies like CSS stylesheets and require you to use inline styles all around, and clients like Outlook don’t even support CSS margins. It’s like a trip to the early days of the internet.

Ensuring your emails look good in a wide array of email clients is also a challenge. For example, it’s not uncommon for emails to look fine in modern clients like Gmail web and look broken in Outlook. The email client market is so fragmented that one couldn’t possibly test emails in every client (and version) that your customers use. Most of these clients weren’t built with debugging in mind, making it difficult to identify and fix those bugs.

To make this problem more complex, about 67% of users opened emails on their mobile devices in 2018, and this trend will likely keep increasing. This means we have to design and build our email templates in a way that looks good on both desktop and mobile.

So! I’m going to tell you about the system we built using MJML, Nunjucks and Email on Acid to build great emails without going bananas.

MJML

In our quest to handle responsive emails gracefully, we stumbled upon MJML, a framework that provides a standard set of components for handling responsive emails in a way that is compatible across most email clients.

Since MJML is XML-based, it would be familiar to those used to HTML, but it’s also a very opinionated framework that has specific ways of doing things. For example, you can’t just use the style HTML property. Instead, you need to use the properties MJML makes available on its components (background-color, align, etc). This rigidness is by design, and that’s what enables MJML to render markup compatible across many email clients. This is a compromise with which we felt comfortable.

This is an example of what a simple MJML template looks like:

<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-image width="100px" src="/logo-small.png"></mj-image>
<mj-divider border-color="#F45E43"></mj-divider>
<mj-text font-size="20px" color="#F45E43">
Hello World
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

Source: mjml.io

This is what it ends up looking like:

Source: mjml.io

Because browsers cannot interpret MJML markup (forget about email clients), there’s a “compilation” step that needs to happen to transpile your MJML template into HTML. The generated HTML is ugly, but it does the job. You can take my word for it or you can inspect it yourself by clicking ‘View HTML’ in the live editor (spoiler alert, it’s a bunch of tables).

In order to translate your templates, the MJML package provides a CLI tool and also a Node module so you can compile your templates programmatically. This is how you would compile an MJML string with Node:

import mjml2html from 'mjml'/*
Compile an mjml string
*/
const htmlOutput = mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`, options)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)

Source: https://mjml.io/documentation/

MJML also offers a good amount of tooling for various editors and a bunch more are created by the community. These range from reusable components to parsers in other programming languages. Make sure to check those out if you are planning to take on MJML.

Nunjucks

Source: https://mozilla.github.io/nunjucks/

We found that MJML does a great job with handling responsive emails across most email clients, but the templates are static. That’s all right if you are sending the same email to all of your users, but if your emails need to have variable content, you need to add an extra layer.

This is where Nunjucks comes in; it is a templating engine for JavaScript developed by Mozilla. It supports most of the features other templating engines like Jinja2 or Pug (formerly Jade) provide and it is a perfect match with MJML. This is how a Nunjucks template might look:

{% extends "base.html" %}{% block header %}
<h1>{{ title }}</h1>
{% endblock %}

{% block content %}
<ul>
{% for name, item in items %}
<li>{{ name }}: {{ item }}</li>
{% endfor %}
</ul>
{% endblock %}

Source: https://mozilla.github.io/nunjucks/

Nunjucks supports loops, conditionals, interpolation, and file importing, which allow you to modularize and reuse parts of your templates. It also requires a compilation step in order to generate the templates. In order to render the template above, we could use something similar to:

import nunjucks from 'nunjucks';var res = nunjucks.render('foo.html', { 
title: 'Hello Nunjucks',
items: {
apples: 'red',
lemons: 'yellow',
grapes: 'purple'
}
});
console.log(res);/*
The output would be somewhat like:
<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello Nunjucks</h1>
<ul>
<li>apples: red</li>
<li>lemons: yellow</li>
<li>grapes: purple</li>
</ul>
</body>
</html>
*/

MJML + Nunjucks

Now that we have a grasp on MJML and Nunjucks, pairing them is simple. This is how the previous example would look if we added MJML:

<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-size="20px">
<h1>{{ title }}</h1>
</mj-text>
<mj-text>
<ul>
{% for name, item in items %}
<li>{{ name }}: {{ item }}</li>
{% endfor %}
</ul>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

And this is how we’d need to compile it:

import nunjucks from 'nunjucks';
import mjml2html from 'mjml'
//need to compile nunjucks first so that the MJML parser doesn't choke
var res = nunjucks.render('foo.html', {
title: 'Hello Nunjucks',
items: {
apples: 'red',
lemons: 'yellow',
grapes: 'purple'
}
});

const htmlOutput = mjml2html(res)
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)

Email on Acid

Once we got our MJML + Nunjucks setup, we needed a way to verify that our emails were being rendered correctly in different email clients. There’s a number of tools offering this service, but we chose Email on Acid. We’re really happy with it.

Screenshot of Email on Acid showing various versions of mobile views

Source: https://www.emailonacid.com/

Once you create an account, Email on Acid provides you with an email address you can send tests to, and those test emails will be automatically processed in a bunch of email clients. Because this tool can test a lot of email clients, you may want to tweak your filters so they’re specific to your users in order to avoid some noise.

The verdict

The stack we used previously for our email templates was deprecated and really tough to work with. The setup described above is a major quality of life improvement for us (it’s simple!) and we recommend it to anyone who needs to work with emails.

A word of caution: if your brand/design teams are as ambitious as ours, you could find instances in which MJML is limiting, but it offers ways to “break out” from the framework and use raw HTML. This could potentially break the responsiveness of your templates, and so you should test extensively if you need to do that.

Overall, we have had a positive experience with MJML, Nunjuck, and Email on Acid and encourage the use of this pairing of libraries and services. If you are curious about how our invitation emails look, here’s one I made recently!:

Source: https://www.paperlesspost.com/flyer

Do you use a different stack? let us know in the comments! Also if you liked this article make sure to 👏 below so it reaches more people.

--

--