Skip to main content

Creating HTML emails is one of the most frustrating tasks for developers. Unlike modern web development, where frameworks like React or Tailwind make things easier, email development still feels like coding for the early web era. Email clients vary widely in how they render HTML and CSS, making it difficult to create emails that look good across all platforms.

If you’ve ever struggled to make your email design work on both Gmail and Outlook, you’re not alone.

I remember spending hours tweaking table layouts and inline styles just to get an email to display correctly on both Outlook and Gmail—only to find out it broke on mobile. The trial-and-error process can be maddening, especially when small changes cause unexpected results.

The Problem with HTML Emails

Email technology has barely evolved in the last two decades. Many email clients don’t support modern CSS properties or responsive design techniques. Developers often resort to nested tables and inline styles—techniques long abandoned in web development.

Simple tasks like centering text or adding a background color require awkward workarounds. The widely used @media query, essential for responsive designs, has very limited support across email clients (check compatibility here).

To create consistent layouts, developers must write complex code that’s hard to maintain and debug.

What is MJML?

MJML (Mailjet Markup Language) is an open-source framework that simplifies responsive email development. Built with Node.js, MJML uses a custom markup language similar to HTML but optimized for email clients. The framework automatically translates MJML code into responsive, email-friendly HTML with nested tables and inlined CSS.

If you can’t achieve your desired design with MJML, chances are it isn’t possible in email development at all.

Why Use MJML?

  • Write clean, high-level code without worrying about email client quirks.
  • Built-in support for responsive layouts.
  • Pre-designed components like buttons, dividers, and images.
  • Automatic compatibility with most email clients.
  • Faster development—up to 4x faster than traditional methods according to MJML.

How to Get Started

Install MJML via npm:

npm install mjml

You can also use MJML plugins for IDEs like Intellij or Visual Studio Code, which offer live previews directly in your editor.

Basic Example

Here’s how simple it is to create an email template with MJML:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>Hello, World!</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

MJML converts this into complex HTML with tables, ensuring compatibility across all major email clients.

<!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">
    @media only screen and (min-width:480px) {
      .mj-column-per-100 {
        width: 100% !important;
        max-width: 100%;
      }
    }
  </style>
  <style media="screen and (min-width:480px)">
    .moz-text-html .mj-column-per-100 {
      width: 100% !important;
      max-width: 100%;
    }
  </style>
  <style type="text/css">
  </style>
</head>

<body style="word-spacing:normal;">
  <div style="">
    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
    <div style="margin:0px auto;max-width:600px;">
      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
        <tbody>
          <tr>
            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
                  <tbody>
                    <tr>
                      <td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
                        <div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">Hello, World!</div>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </div>
              <!--[if mso | IE]></td></tr></table><![endif]-->
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <!--[if mso | IE]></td></tr></table><![endif]-->
  </div>
</body>

</html>

Responsive Layouts Out of the Box

Want a two-column layout that stacks on mobile? Just add another <mj-column>:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>Hello, Column 1!</mj-text>
      </mj-column>
      <mj-column>
        <mj-text>Hello, Column 2!</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Without any additional media queries, MJML automatically stacks the columns on smaller screens.

Advanced Features

MJML also supports:

  • Conditional content based on email clients (e.g., hide elements in Outlook).
  • Custom components with dynamic attributes.
  • Built-in style resets.

Performance & Workflow

In my experience, MJML has drastically reduced the time spent on email development. I embed dynamic attributes directly into MJML templates, and they remain intact when converting to HTML—perfect for CRM integrations or any web applications where you have dynamic attributes.

While I haven’t tested MJML in CI/CD pipelines, automating the build process should be straightforward. Simply make sure your CI/CD installs the npm package and runs the MJML CLI to generate HTML file.

Conclusion

MJML makes email development faster, cleaner, and more reliable. If you’re still writing raw HTML emails, give MJML a try. The framework handles the hard parts for you, so you can focus on creating beautiful designs.

Test it yourself with their Live Editor—once you try MJML, you won’t want to go back.

Leave a Reply


The reCAPTCHA verification period has expired. Please reload the page.