The MJML ecosystem provides a robust set of tools for creating responsive HTML emails without the pain of traditional table-based layouts. mjml serves as the primary Node.js library for programmatic rendering, while mjml-cli offers a command-line interface for build pipelines and quick compilation. mjml-core exposes the underlying engine for developers who need to create custom tags or extend the language itself. Finally, mjml-react allows teams to write MJML templates using React JSX syntax, enabling component reuse and type safety within existing React codebases. Together, these packages cover the full spectrum of email development needs — from simple static files to dynamic, component-driven systems.
The MJML ecosystem simplifies responsive email development by abstracting complex table layouts into semantic components. However, choosing between mjml, mjml-cli, mjml-core, and mjml-react depends on your workflow — whether you need a CLI tool, a Node.js library, a JSX wrapper, or low-level engine access. Let's compare how they handle common engineering tasks.
mjml is the main package for Node.js integration.
// mjml: Standard Node.js usage
import mjml2html from 'mjml';
const { html } = mjml2html(`<mjml><mj-body>Hello</mj-body></mjml>`);
mjml-cli is a command-line tool.
# mjml-cli: Command Line usage
npx mjml input.mjml -o output.html
mjml-core is the underlying engine.
// mjml-core: Engine extension
import { Body } from 'mjml-core';
// Registering a custom component requires core access
export default class CustomBody extends Body { /*...*/ }
mjml-react is a React wrapper.
// mjml-react: JSX usage
import { Mjml, MjmlBody, MjmlText } from 'mjml-react';
const Template = () => (
<Mjml><MjmlBody><MjmlText>Hello</MjmlText></MjmlBody></Mjml>
);
mjml runs inside your JavaScript code.
// mjml: Dynamic rendering in app code
const template = `<mjml><mj-body>${content}</mj-body></mjml>`;
const { html } = mjml2html(template);
mjml-cli runs as a separate process.
# mjml-cli: Batch compilation
mjml ./templates/*.mjml -o ./dist
mjml-core runs as part of a custom build.
// mjml-core: Custom tag processing
import mjmlCore from 'mjml-core';
const result = mjmlCore(mjmlString, { components: [CustomTag] });
mjml-react renders via React tree.
// mjml-react: Render to string then compile
import render from 'mjml-react';
import { html } from 'mjml';
const mjmlString = render(<Template />);
const { html: finalHtml } = mjml2html(mjmlString);
mjml uses standard tags out of the box.
mjml-core for extensions.// mjml: Limited to existing tags
// Cannot define <mj-custom-tag> here directly
mjml-cli supports custom tags via config.
# mjml-cli: Loading custom components
mjml input.mjml --config.components ./my-components.js
mjml-core is built for extensions.
// mjml-core: Creating a new tag
import { BodyComponent } from 'mjml-core';
export default class MjmlHello extends BodyComponent {
render() { return `<div>Hello</div>`; }
}
mjml-react wraps standard tags.
// mjml-react: Custom React wrapper
const Hello = () => <MjmlText>Hello World</MjmlText>;
// Uses standard tags under the hood
mjml returns errors in the result object.
errors array after compilation.// mjml: Check errors in code
const { html, errors } = mjml2html(badMjml);
if (errors.length) { console.error(errors); }
mjml-cli exits with non-zero code on failure.
# mjml-cli: Fail build on error
mjml input.mjml || echo "Build failed"
mjml-core throws or returns validation issues.
// mjml-core: Manual validation
try {
mjmlCore(input, { validationLevel: 'strict' });
} catch (e) { /* handle error */ }
mjml-react relies on React prop types.
// mjml-react: Runtime prop checks
// <MjmlText font-size="invalid" /> might warn in dev
While the entry points differ, all packages rely on the same rendering engine.
// All packages eventually call the same core logic
// Output HTML structure remains the same
mjml includes mjml-cli and mjml-core.mjml-react depends on mjml for final compilation.// Package relationships
// mjml -> mjml-core
// mjml-react -> mjml
<mj-section>, <mj-column>, <mj-text>.// Consistent component names
// <mj-text> in MJML vs <MjmlText> in React
| Feature | mjml | mjml-cli | mjml-core | mjml-react |
|---|---|---|---|---|
| Primary Use | Node.js Library | Command Line | Engine Extension | React JSX |
| Integration | Import in JS | Terminal / Script | Custom Dev | React App |
| Custom Tags | No (use core) | Via Config | Yes (Native) | Via Components |
| Dynamic Data | Yes (Runtime) | No (Static) | Yes (Custom) | Yes (Props) |
| Learning Curve | Low | Lowest | High | Medium (React) |
mjml is the standard choice 🧰 for backend developers needing to generate emails within a Node.js app. It balances ease of use with programmatic control.
mjml-cli is the operations choice 🛠️ for DevOps or frontend build pipelines where templates are static files. It requires no code to run.
mjml-core is the advanced choice 🔧 for library authors or teams building a custom design system that needs new tags not found in the default set.
mjml-react is the frontend choice ⚛️ for React teams who want to treat emails like any other component in their app, leveraging props and composition.
Final Thought: For most applications, start with mjml for flexibility or mjml-cli for simplicity. Only reach for mjml-core if you must extend the language, and choose mjml-react if your team's React expertise outweighs the added build complexity.
Choose mjml if you are building a Node.js application that needs to generate emails dynamically on the server. It is the standard library for integrating MJML rendering into backend services, API routes, or build scripts where you need direct control over the compilation process via JavaScript.
Choose mjml-cli if you want a zero-code solution for compiling templates during your build process or CI/CD pipeline. It is ideal for static sites or projects where templates are stored as .mjml files and simply need to be converted to .html without writing custom Node.js scripts.
Choose mjml-core only if you are an advanced user needing to register custom components or modify the MJML engine behavior. It is not for general template rendering but rather for extending the MJML language itself with new tags or logic that the standard library does not support.
Choose mjml-react if your team is already heavily invested in React and wants to leverage JSX for email templates. It is perfect for projects that benefit from component composition, props typing, and JavaScript logic within the template structure, rather than writing raw MJML markup.
| Introduction | Installation | Usage |
MJML is a markup language created by Mailjet and designed to reduce the pain of coding a responsive email. Its semantic syntax makes the language easy and straightforward while its rich standard components library shortens your development time and lightens your email codebase. MJML’s open-source engine takes care of translating the MJML you wrote into responsive HTML.
You can install MJML with NPM to use it with NodeJS or the Command Line Interface. If you're not sure what those are, head over to Usage for other ways to use MJML.
npm install mjml
Don't want to install anything? Use the free online editor!
MJML comes with tools and plugins, check out:
For more tools, check the Community page.
Compiles the file and outputs the HTML generated in
output.html
mjml input.mjml -o output.html
You can pass optional arguments to the CLI and combine them.
| argument | description | default value |
|---|---|---|
mjml [input] -o [output] | Writes the output to [output] | |
mjml [input] -s | Writes the output to stdout | |
mjml [input] -s --noStdoutFileComment | Writes the output to stdout without file comment in the first line | |
mjml -w [input] | Watches the changes made to [input] (file or folder) | |
mjml [input] --config.allowIncludes | Enables mj-include processing (true or false) | false |
mjml [input] --config.allowMixedSyntax | Allows mixing block and CSS variable syntax when sanitizeStyles is enabled (true or false) | false |
mjml [input] --config.beautify | Beautifies the output (true or false) | true |
mjml [input] --config.includePath | Adds allowlisted include root(s), as a string path or JSON array of paths | |
mjml [input] --config.minify | Minifies the output (true or false) | false |
mjml [input] --config.minifyOptions | Options for HTML minifier, use minifyCss to control CSS minification | See mjml-cli documentation |
mjml [input] --config.juicePreserveTags | Preserve some tags when inlining CSS | See mjml-cli documentation |
mjml [input] --config.mjmlConfigPath | Path to .mjmlconfig file for custom components | current working directory |
mjml [input] --config.sanitizeStyles | Sanitizes template variables inside CSS before minification (true or false) | false |
mjml [input] --config.useMjmlConfigOptions | Allows to use the options attribute from .mjmlconfig file | false |
mjml [input] --config.templateSyntax | Sets custom template delimiters as JSON array ([{"prefix":"{{","suffix":"}}"}]) | [{"prefix":"{{","suffix":"}}"},{"prefix":"[[","suffix":"]]"}] |
See mjml-cli documentation for more information about config options.
import mjml2html from 'mjml'
/*
Compile an mjml string
*/
async function renderMjml() {
const htmlOutput = await 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)
}
renderMjml()
You can pass optional options as an object to the mjml2html function:
| option | unit | description | default value |
|---|---|---|---|
| allowMixedSyntax | boolean | Allows mixed block/value/property template syntaxes during CSS sanitization | false |
| beautify | boolean | Option to beautify the HTML output | false |
| filePath | string | Path of file, used for relative paths in mj-include instances | . |
| fonts | object | Default fonts imported in the HTML rendered by MJML | See in index.js |
| ignoreIncludes | boolean | Option to ignore mj-include instances | true |
| includePath | string or string[] | Additional allowlisted include root(s), used when ignoreIncludes is false | |
| juicePreserveTags | object | Preserve some tags when inlining CSS, see documentation for more info | |
| keepComments | boolean | Option to keep comments in the HTML output | true |
| minify | boolean | Option to minify the HTML output | false |
| minifyOptions | object | Options for htmlnano minification (including minifyCss), see documentation for more info | |
| mjmlConfigPath | string | The path or directory of the .mjmlconfig file (for custom components use) | process.cwd() |
| preprocessors | array of functions | Preprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string | [] |
| sanitizeStyles | boolean | Sanitizes template variables in CSS before minification | false |
| templateSyntax | array of objects | Custom template delimiters used by sanitization ([{ prefix, suffix }]) | [{"prefix":"{{","suffix":"}}"},{"prefix":"[[","suffix":"]]"}] |
| useMjmlConfigOptions | boolean | Allows to use the options attribute from .mjmlconfig file | false |
| validationLevel | string | Available values for the validator: strict, soft, skip | soft. |
var mjml2html = require('mjml-browser')
/*
Compile a mjml string
*/
mjml2html(`
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>
Hello World!
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`, options).then(function (htmlOutput) {
/*
Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)
})
A free-to-use MJML API is available to make it easy to integrate MJML in your application. Head over here to learn more about the API.
MJML wouldn't be as cool without its amazing community. Head over the Community Slack to meet fellow MJML'ers.