CLI tool
mjmlio/mjml avatar
mjmlio/mjml

MJML: writing responsive email as markup instead of tables

MJML: the only framework that makes responsive email easy

18,252 stars1,003 forksJavaScriptMIT

At a glance

What is it?
MJML is a markup language and compiler that turns semantic tags into the table-based HTML email clients accept. It suits teams who ship templates repeatedly and can live with a build step.
Who is it for?
Adopt MJML if your team maintains several email templates and wants them in version control as readable markup rather than hand-written tables. Skip it if you send one plain-text-ish newsletter a year, or if your ESP only accepts HTML pasted into a browser editor with no build step.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem MJML targets: email HTML is not web HTML

Email clients do not render the same HTML a browser does. The practical consequence is that hand-written email markup ends up as nested tables, inline styles and conditional comments, and the same layout has to be re-checked in every client you support. MJML approaches this from the other direction: you write semantic tags such as mj-section, mj-column and mj-text, and the compiler emits the table structure and inline CSS. The README frames the goal as reducing "the pain of coding a responsive email" and describes the engine as translating MJML into responsive HTML.

The audience is narrow and specific. It is for developers and template authors who produce email repeatedly and want the source in a repository, not for someone who sends a single announcement a year. It is also a poor fit for anyone who needs pixel-level control over the final markup: once the compiler has expanded your tags, the output is generated code, and editing it by hand defeats the purpose.

How the compiler turns mj-section into tables

The repository is a Lerna monorepo. The root package.json declares workspaces under packages/* and a build script that runs lerna run build --parallel --ignore mjml-browser, with a separate build-browser script for the browser bundle. That layout tells you the engine, the CLI and the browser build are separate packages rather than one file.

The data flow is a single pass: you supply an MJML string, the compiler parses the tag tree, resolves components (including custom ones registered through a .mjmlconfig file), and produces an HTML string plus an errors array. The Node.js example in the README shows the return value being logged directly, and the comment says it prints "the responsive HTML generated and MJML errors if any". That pairing matters: compilation can succeed with warnings, so a pipeline that only checks for a thrown exception will miss malformed content.

Styling is handled during compilation rather than at render time. Options such as beautify, minify and sanitizeStyles control the output, and the CLI exposes the same switches as --config.* flags. The default for beautify is true and for minify is false, which is a sensible default for debugging and a wasteful one for production sends.

Installing MJML and compiling a first template

The README gives one installation path: npm. If you want the command line interface or the Node.js API, install the package first.

bash
npm install mjml

With that installed, write a minimal template. The README's Node.js example uses this structure, with mj-body wrapping a section and column.

bash
mjml input.mjml -o output.html

This compiles the file and writes the generated HTML to output.html, which is exactly how the README describes the CLI. To inspect the result without creating a file, pass -s to write to stdout. If you are iterating on a template, mjml -w input.mjml watches the file or folder and recompiles on change.

For programmatic use, the README exports a function that returns a promise.

javascript
import mjml2html from 'mjml'

const htmlOutput = await mjml2html(`
  <mjml>
    <mj-body>
      <mj-section>
        <mj-column>
          <mj-text>Hello World!</mj-text>
        </mj-column>
      </mj-section>
    </mj-body>
  </mjml>
`)

The returned object carries both the HTML and any MJML errors, so log the whole object before wiring it into a send job. One default to note early: mj-include processing is disabled unless you pass --config.allowIncludes true, and includePath restricts which roots includes may resolve from.

Where MJML gets in your way

The compiler owns the output. That is the trade. If a client-specific fix is not expressible through MJML components and attributes, you either write a custom component (registered via mjmlConfigPath pointing at a .mjmlconfig file, with useMjmlConfigOptions controlling whether that file's options attribute is honoured) or you post-process the generated HTML. Neither is free, and the README does not document a rollback path for a bad template beyond keeping the previous generated HTML.

Includes are off by default, which surprises people who split a header and footer into separate files. You have to opt in with --config.allowIncludes and, if your includes live outside the working directory, declare the roots through --config.includePath. The README does not document rollback or a dry-run mode for the CLI, so a broken template shows up as a compile error or as bad output, not as a staged failure.

Template variables add another constraint. sanitizeStyles defaults to false, and allowMixedSyntax, which permits mixing block and CSS variable syntax during sanitization, also defaults to false. If your templates use delimiters other than the defaults, templateSyntax takes a JSON array such as [{"prefix":"{{","suffix":"}}"}]. Getting these wrong tends to surface as mangled CSS after minification rather than as a clear error.

MJML against hand-written HTML and template engines

The obvious alternative is writing the table markup yourself, or generating it from a general-purpose template engine such as Handlebars or a framework's view layer. The difference is where the email-specific knowledge lives. With a general engine, you still write the tables, the conditional comments and the inline styles, and you maintain that knowledge in your own codebase. MJML puts it in the compiler: you describe structure, and someone else maintains the translation to client-compatible HTML.

A second alternative is an online editor. The README points to a free online editor at mjml.io/try-it-live, and that is a legitimate route for a one-off template. The difference is versioning. An editor session produces HTML you paste somewhere; a repository of .mjml files produces a diffable history and a repeatable build, which is the whole reason to install the compiler. If neither history nor repetition matters to you, the editor is less work.

Editor plugins exist as well. The README lists a Visual Studio Code plugin with MJML included and a Sublime Text plugin that requires MJML to be installed separately. Those change where you type, not what the compiler does.

Maintenance, releases and the MIT licence

The repository is not archived, and the last push was on 2026-09-10. Releases are frequent enough to suggest a live project rather than a frozen one: v5.4.1 on 2026-09-10, v5.4.0 on 2026-06-29 and v5.3.0 on 2026-05-27. That cadence is the useful signal here, more than any count of stars.

The upgrade cost sits in the monorepo's toolchain. Development requires yarn, and the README's steps are git clone, yarn, yarn build, with yarn build:watch for rebuilds. The root package.json pins a long resolutions block covering transitive dependencies such as @babel/runtime, cross-spawn and handlebars, which means upgrading MJML can pull a coordinated set of dependency bumps rather than a single version change. Budget for running the test suite (yarn test) and the lint step after an upgrade.

The licence is MIT. In practical terms that permits commercial use and modification, and it requires that the licence text and copyright notice travel with redistributed copies. It says nothing about the email clients you target or the deliverability of what you send, and it is not legal advice; check with your own counsel if you are redistributing a modified compiler.

Editorial conclusion

Adopt MJML if your team maintains several email templates and wants them in version control as readable markup rather than hand-written tables. Skip it if you send one plain-text-ish newsletter a year, or if your ESP only accepts HTML pasted into a browser editor with no build step. Before committing, verify three things: that mjml input.mjml -o output.html produces HTML your target clients render as expected, whether your templates need mj-include (it is off unless you pass --config.allowIncludes), and whether the minify and sanitizeStyles defaults fit your pipeline. The repository was last pushed on 2026-09-10 and the most recent release is v5.4.1.

Frequently asked questions

What does MJML stand for?

The README does not expand the acronym. It describes MJML as a markup language created by Mailjet and designed to reduce the pain of coding a responsive email, and the project name is used as-is throughout the documentation.

How can I convert an MJML file to HTML?

Install the package with npm install mjml, then run mjml input.mjml -o output.html to write the generated HTML to a file, or pass -s to write it to stdout. From Node.js, the default export takes an MJML string and returns an object containing the HTML and any MJML errors.

Is MJML free?

Yes. The repository is licensed under MIT, which permits commercial use and modification provided the licence text and copyright notice are kept with redistributed copies. The README also points to a free online editor at mjml.io/try-it-live.

Official sources

  1. License: MIT
  2. mjmlio/mjml on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mjmlio-mjml.svg)](https://hysenlabs.com/projects/mjmlio-mjml)