Hand-coding a responsive HTML email usually means three or four layers of nested tables, Outlook-only conditional comments, and a fresh round of client testing every time someone asks for a wider button. MJML removes that layer of work. Maintained by Mailjet and released under the MIT license, MJML “is a markup language designed to reduce the pain of coding a responsive email,” per its own documentation. Instead of writing raw <table> markup, you write semantic tags like <mj-section> and <mj-column>, and MJML’s compiler generates the nested tables, inline styles, and Outlook conditional comments for you. The output is plain HTML, so it works with any sending method, from a provider API to a Node.js script that pipes the compiled string straight into how you send HTML email.
Why Responsive Email Still Means Fighting Tables and Outlook
Two problems make hand-coded responsive email harder than a responsive web page. First, most email clients strip or ignore modern CSS. MJML’s own site puts it plainly: “Gmail for example, removes the entire header section of your email’s HTML. Guess what happens if that’s where you added your CSS? Yes, it’s removed.” That forces developers to rely on inline styles instead of a shared stylesheet, so every color, font, and padding value ends up repeated on every element.
Second, desktop Outlook (2007 through the current Word-based builds) renders HTML using Microsoft’s Word engine rather than a browser engine. As Litmus explains, “these use Word as the rendering engine, which made sense at a time when email was like writing letters,” but it also means Outlook ignores CSS properties that every modern browser supports and needs its own conditional-comment markup to keep spacing consistent. A real responsive email template ends up closer to nested <table> elements, <!--[if mso]> blocks, and duplicated inline styles than to the CSS a front-end developer would reach for on the web. That’s the same skeleton covered in Email Layout and How to Build a Responsive HTML Email Template; MJML’s pitch is that you write the semantic version once and let its compiler produce that skeleton for you.
MJML’s Component Model: Semantic Tags Instead of Nested Tables
MJML structures every template the same way: an <mjml> root wraps <mj-head> (fonts, styles, preview text) and <mj-body> (the visible content). Inside <mj-body>, <mj-section> defines a horizontal row and <mj-column> splits that row into columns that stack automatically on narrow screens, which is the responsive half of “responsive email framework.” Content components nest inside columns: <mj-text> for copy, <mj-image> for responsive images, <mj-button> for a cross-client CTA button, plus less common ones like <mj-divider>, <mj-spacer>, <mj-social>, and <mj-navbar>.
Two components matter for anything the standard library doesn’t cover. <mj-style> “allows you to set CSS styles that will be applied to your MJML document as well as the outputted HTML,” and can inline that CSS with inline="inline", per the official component reference. <mj-raw> does the opposite: it “displays raw HTML that is not parsed by the MJML engine,” which is the escape hatch for anything the component library doesn’t model, from a tracking pixel to a hand-tuned Outlook table.
On GitHub, where the project describes itself as “the only framework that makes responsive-email easy”, MJML also supports custom components: a layout problem you hit repeatedly can be packaged as a reusable <mj-*> tag instead of copy-pasted markup across templates.
From Markup to Compiled HTML: A Real Example
A short example shows what the compiler actually does. This button, written once as MJML:
<mj-button href="https://example.com/confirm" background-color="#2563EB">
Confirm your email
</mj-button>
compiles, using the official CLI (MJML v5.4.0), to this:
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tbody>
<tr>
<td align="center" bgcolor="#2563EB" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#2563EB;" valign="middle">
<a href="https://example.com/confirm" style="display:inline-block;background:#2563EB;color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;" target="_blank">
Confirm your email
</a>
</td>
</tr>
</tbody>
</table>
One line of markup becomes a table with a bgcolor fallback for clients that ignore background, an mso-padding-alt property that only Outlook reads, and inline styles duplicated across the <td> and <a> so the button renders consistently whether or not the surrounding CSS survives. That’s not something worth writing by hand more than once, which is the entire argument for MJML.
Tooling: CLI, Live Editor, and Framework Integrations
MJML ships as the mjml npm package. Installing it (npm install mjml) gives you a CLI that compiles a file directly:
mjml input.mjml -o output.html
Add -w to recompile on save, or call the mjml2html function directly from Node if you’re generating HTML programmatically, for a transactional pipeline, rather than compiling static files. Community ports extend that same compiler beyond JavaScript, with Python, .NET, Ruby, and PHP/Laravel wrappers for teams whose backend isn’t Node.
If you’d rather not install anything, MJML’s online editor compiles and previews templates directly in the browser, useful for a quick edit or for sharing a template with someone who isn’t set up to run the CLI. There’s also an official VS Code extension with live preview and linting, and a Gulp plugin for teams that already build static assets with a Gulp pipeline.
Using MJML from a React codebase specifically works through mjml-react, a community-maintained wrapper that lets you write <MjmlSection> and <MjmlColumn> as JSX and calls the same compiler underneath. That’s a different tool from React Email, covered next: mjml-react is MJML’s syntax wrapped in JSX, while React Email is a separate component library with its own markup and no MJML dependency.
MJML only handles the compile step. Retries, deliverability, and the provider API still have to come from somewhere, which is the layer Coldletter’s templating and delivery tooling covers end to end rather than leaving you to wire a separate ESP on top of a self-hosted compiler.
MJML vs. React Email vs. Hand-Coded HTML
| MJML | React Email | Hand-coded HTML | |
|---|---|---|---|
| Syntax | XML-like <mj-*> tags | JSX/TSX React components | Raw <table>/<td> markup |
| Compiles to | Responsive HTML (nested tables, inline CSS) | Responsive HTML via render() | N/A, you write the final HTML directly |
| Type checking | None (schema validation only) | Full TypeScript support | None |
| Best fit | Teams without a JS/Node backend, via community ports, or anyone who wants a dedicated templating language | Teams already shipping a React/Node codebase who want type-safe props | One-off emails or maintaining an existing legacy template |
| Preview tooling | Online editor, VS Code extension | Built-in local dev server | Browser plus dedicated email testing tools |
Neither library eliminates the underlying rendering problems described above; both abstract them behind a compiler. The choice between them mostly comes down to your stack. React Email fits naturally when your templates already live in a Node/React codebase and you want type-safe props; its own documentation frames the goal as letting developers “build amazing emails without having to deal with the mess of creating table-based layouts and maintaining archaic markup.” MJML fits when you want a templating syntax that isn’t tied to any single application language, or your team already knows the <mj-*> tags from using MJML elsewhere. Once either one compiles to HTML, the visual and layout decisions from there, spacing, hierarchy, dark mode, are the same ones covered in Email Design Best Practices.
Frequently Asked Questions
Is MJML free to use?
Yes. MJML is open source under the MIT license and published as the mjml npm package at no cost to install or run. The only cost is whatever you use to send the compiled HTML: an SMTP account or a transactional email provider’s API.
Is MJML still maintained?
Yes. Mailjet continues to release new versions; the latest, v5.4.0, shipped in late June 2026 with new attributes for mj-social-element and mj-section, alongside dependency updates. The project has had 126 releases to date.
Do I need Node.js to use MJML?
The official CLI and npm package require Node.js. If your backend isn’t Node, community ports (Python, .NET, Ruby, PHP/Laravel) compile the same MJML syntax without a Node dependency, though they tend to track the JavaScript version’s feature set with some lag.
Can I use MJML with React?
Yes, through mjml-react, a community-maintained wrapper that lets you write MJML components as JSX and calls the MJML compiler underneath. That’s different from React Email, which is a separate library with its own component set rather than a JSX wrapper around MJML.
Does MJML support dark mode or AMP for email?
Not natively. MJML’s component library doesn’t include a dedicated dark-mode or AMP tag, so both need the same escape hatches: <mj-style> for a prefers-color-scheme media query, or <mj-raw> to inject markup MJML doesn’t otherwise model.
Do I still need to know HTML to use MJML?
Not table-layout HTML specifically. You still set styling through component attributes and, occasionally, raw CSS in <mj-style>, but MJML’s compiler generates the nested <table> structure, Outlook conditional comments, and inline styles for you instead of you writing them by hand.
I’ve spent my career building software at scale with a soft spot for email: deliverability, lifecycle campaigns, and getting messages to actually land. I started Coldletter to fix what bugged me about transactional and marketing email tools. I’m based in Vancouver.
