My Wonderful HTML Email Workflow

If you’ve ever tried to build an HTML email from scratch, you know that it’s a gnarly adventure.

It feels quite a bit like taking a trip back in time. Email clients don’t support modern luxuries like CSS Grid, or even Flexbox. Instead, we need to resort to using HTML

tags. Plus, there are dozens of email clients, each with their own quirks and idiosyncracies.

When I first started my newsletter, I naively tried to build my own HTML emails from scratch. Even after a bunch of research and testing, I would still regularly hear from folks who’d tell me that my email doesn’t render properly for them.

So, I tore it all down and built a new system from scratch. I had a pretty hefty wishlist for this new system:

  • Emails should be fully compatible with all popular email clients, and I shouldn’t have to do any manual testing.

  • I shouldn’t have to write a single

tag by hand. I should be able to work at a higher level of abstraction, letting tools generate the raw HTML for me.

  • For composing individual emails, I should be able to write in a Markdown-like syntax. It should feel like editing a word document, not creating an HTML file.

  • I should be able to create my own custom components, and reuse them across different emails, like in any React app.

  • Each email should also produce a web version, at a unique URL. Each newsletter sent should automatically have a “View on Web” link dynamically inserted, linking to the web version.

  • I’m happy to say, I met all of these goals! Writing new emails is as easy as writing new blog posts. I jot down some Markdown, include some handy custom React components, and copy/paste the resulting HTML into my newsletter tool. No muss, no fuss.

    Want to check out the resulting email? You can view a recent newsletter issue.

    Let’s talk about how it works.

    Not a comprehensive tutorial!

    This blog post is meant to serve as a high-level overview, something you can use as a compass when it comes to your own email workflow. It is not a step-by-step tutorial.

    There’s two reasons for this:

    • The exact instructions will depend on the stack you already use.

    • This stuff is hard, and a truly comprehensive tutorial would be 10x this length.

    So, my hope is that this points you in the right direction, but you’ll definitely need to do a fair chunk of work to implement a similar system!

    MJML is a responsive email framework from Mailjet. It essentially provides a layer of abstraction over raw HTML.

    The idea is that the folks on the MJML team have done the painstaking work of figuring out all of the quirks across dozens of email clients, and they’ve baked all of the fixes and adjustments in. As long as you follow the MJML conventions, your email should render properly across all email clients.

    Here’s an example of an MJML email:

    When it’s compiled, it produces a big chunk of client-friendly HTML:

    The true output from that example email is much longer, over 100 lines of code. If you’re curious, you can view the full thing using MJML’s live REPL.

    A full MJML tutorial is beyond the scope of this blog post, but let’s go over the basic idea.

    Link to this heading

    MJML building blocks

    The MJML language provides a set of common tags you can use to structure your email.

    Each email is a collection of sections, using the tag. Sections can’t be nested. Each section is meant to be a distinct visual chunk of the email.

    Each section should have one or more columns, using . On a large screen, sections will sit side-by-side, as if in a Flex row. On smaller screens, though, the columns stack vertically. This is the fundamental thing that makes MJML emails “responsive”.

    Within the columns, we add our content. There are a ton of MJML tags for various things, like , which renders a stretchy responsive image. It doesn’t exactly map onto an tag — for example, we can add an href attribute, and it’ll wrap that image in an anchor tag, linking to the provided URL.

    Curiously, all text elements (paragraphs and headings) use the same tag, . You can create headings by applying cosmetic styles as inline attributes, like:

    MJML uses a minimal subset of CSS. There’s no margin property in MJML. Instead, most elements accept a padding prop, or you can use a spacer element with (this isn’t as egregious as it might seem!).

    There are definitely some gaps in MJML. For example, there’s no way in MJML to create lists! Fortunately, there’s an escape hatch. With the tag, you can embed whatever HTML you want:

    MJML won’t process anything inside an tag. This is a double-edged sword. You’re granted the full flexibility of HTML, but without its guardrails, you’re no longer guaranteed to have a consistent, universal experience across all email clients.

    Finally, there are some handy pre-built utilities you can use. For example, you can add social sharing links with , or expandable text chunks similar to details/summary with .

    With these basic building blocks, it’s possible to build most typical email layouts. It’s definitely nowhere near as powerful as modern CSS, and if you have a really ambitious layout, it might not be powerful enough. But for most of us, who just want to build a professional responsive email template, I think it’s a fabulous tool.

    That said, there’s definitely a bit of a learning curve. It takes a while to figure out exactly how all of these pieces fit together, and how to combine them for optimal results.

    For more information, and to learn about all of the building blocks included, be sure to check out the official MJML documentation.

    Does it really work?

    The core promise of MJML is that it produces responsive, client-friendly HTML. Examining the compiled output, it certainly seems to be adding in a ton of stuff, presumably to address various quirks!

    I sent an email generated with MJML to about 30,000 people, asking them to let me know if anything looked busted. Amazingly, I only heard from 2 or 3 people who experienced minor rendering issues. I definitely got more complaints about my hand-crafted HTML emails.

    Of course, MJML can only help us if we follow its conventions. It won’t help us at all if we dump a bunch of custom HTML inside an tag!

    I’m also a bit concerned about the accessibility implications. Because headings and paragraphs use the same tag, I imagine screen readers struggle a bit with the content. Unfortunately, I don’t know very much about the email accessibility space, but this is something I plan on investigating in the future.

    Overall, I think MJML does deliver on its promises, though it’s not perfect. I recently learned about heml, and it looks super compelling!

    Link to this heading

    Compiling MJML

    The MJML tool provides a CLI you can use to transform MJML into HTML:

    You can configure certain options, like how strict the validation should be, or whether the HTML should be minified or not. For the full set of options, check out their command-line docs.

    Intro to the terminal

    If you’re not familiar with command-line interfaces, or how to run commands like this, check out my tutorial on the subject: The Front-End Developer’s Guide to the Terminal.

    In my case, my blog is a Next.js application. Instead of using the CLI tool to generate the HTML, I figured I’d create an API endpoint that would produce and serve the HTML content.

    Here’s what the code would look like:

    When I visit localhost:3000/api/generate-email, the mjml NPM package is used to compile the MJML template into raw HTML. The result is sent to the browser. I can right-click to view the raw HTML source, and copy/paste it into my mailing software.

    One of my core requirements is the ability to create my own components. In addition to and , what if I could produce or ?

    Well, MJML 4 does provide a way to create custom components, but honestly, I didn’t love it.

    I’m a React developer, and I’m generating this email through Next.js, a React framework. So I looked for a way to use React here. And, happily, I found mjml-react, created by the team at Wix.

    Here’s a quick example:

    At first glance, it appears that the mjml-react provides a set of React components we can pop into any ol’ React app, but that isn’t quite right.

    In order for MJML to work properly, it needs to generate an entire HTML document, including the and the . And so we need to call a special render function, which takes a bunch of React components and produces an HTML string.

    If you’re familiar with server-side rendering, you can sorta think of it like the renderToString method from ReactDOMServer. Essentially, we’ll be server-side-rendering this React app into an HTML file, and compiling the MJML at the same time.

    The beautiful thing about this is that it lets us create our own abstractions using typical React.

    Here’s a quick example, a component for generating a standardized link to a blog post:

    Here’s how my API endpoint gets updated:

    It took me a minute to understand what was actually happening here. The render function actually performs two separate tasks:

    1. First, it transforms these React elements into a big MJML string. For example, turns into “<mjml-text>“.

    2. Next, it takes that MJML document and produces the email-safe HTML, same as the compileMjml method we saw earlier.

    Link to this heading


    In general, we don’t create an HTML layout from scratch for every email we send. We create templates, and populate those templates with the content for each unique email.

    We can use this setup to create templates.

    I think it’s easiest if I show you with an example:

    With this