mjml vs mjml-cli vs mjml-core vs mjml-react
Architecting Scalable Email Templates with MJML Ecosystem
mjmlmjml-climjml-coremjml-reactSimilar Packages:

Architecting Scalable Email Templates with MJML Ecosystem

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
mjml018,11015 kB61a month agoMIT
mjml-cli018,11034.8 kB61a month agoMIT
mjml-core018,11087.3 kB61a month agoMIT
mjml-react01,000267 kB3-MIT

MJML Ecosystem: Core, CLI, and React Integration Compared

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.

🛠️ Installation and Entry Points

mjml is the main package for Node.js integration.

  • Install it as a dependency in your project.
  • Import the function to convert MJML strings to HTML.
// 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.

  • Install it globally or as a dev dependency.
  • Run it directly in your terminal or npm scripts.
# mjml-cli: Command Line usage
npx mjml input.mjml -o output.html

mjml-core is the underlying engine.

  • Install it when building custom MJML components.
  • Import the core function to register new tags.
// 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.

  • Install it alongside React dependencies.
  • Write templates as React components returning MJML elements.
// mjml-react: JSX usage
import { Mjml, MjmlBody, MjmlText } from 'mjml-react';

const Template = () => (
  <Mjml><MjmlBody><MjmlText>Hello</MjmlText></MjmlBody></Mjml>
);

⚙️ Rendering Workflow: Script vs Command vs Component

mjml runs inside your JavaScript code.

  • You pass a string or file path to the function.
  • Best for dynamic content where data is fetched at runtime.
// 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.

  • You point it to a directory or file.
  • Best for static templates that do not change per user.
# mjml-cli: Batch compilation
mjml ./templates/*.mjml -o ./dist

mjml-core runs as part of a custom build.

  • You define how tags are processed.
  • Best for creating a proprietary email design system.
// mjml-core: Custom tag processing
import mjmlCore from 'mjml-core';

const result = mjmlCore(mjmlString, { components: [CustomTag] });

mjml-react renders via React tree.

  • You render the component to a string then compile.
  • Best for teams wanting JSX syntax and component reuse.
// 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);

🧩 Extending the Language: Custom Tags

mjml uses standard tags out of the box.

  • You cannot add new tags directly via this package.
  • You must rely on mjml-core for extensions.
// mjml: Limited to existing tags
// Cannot define <mj-custom-tag> here directly

mjml-cli supports custom tags via config.

  • You can point it to a file that registers components.
  • Requires setting up a require path in CLI options.
# mjml-cli: Loading custom components
mjml input.mjml --config.components ./my-components.js

mjml-core is built for extensions.

  • You inherit from base classes to create logic.
  • This is the only way to truly expand MJML capabilities.
// 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.

  • You create React components that output MJML.
  • Easier for React devs but still relies on core engine.
// mjml-react: Custom React wrapper
const Hello = () => <MjmlText>Hello World</MjmlText>;
// Uses standard tags under the hood

🚦 Error Handling and Validation

mjml returns errors in the result object.

  • Check the errors array after compilation.
  • Allows your app to handle failures gracefully.
// 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.

  • Useful for CI/CD pipelines to break builds.
  • Prints errors to standard error stream.
# mjml-cli: Fail build on error
mjml input.mjml || echo "Build failed"

mjml-core throws or returns validation issues.

  • You must handle validation logic manually.
  • Gives full control over how strict the compiler is.
// mjml-core: Manual validation
try {
  mjmlCore(input, { validationLevel: 'strict' });
} catch (e) { /* handle error */ }

mjml-react relies on React prop types.

  • Errors often show as React warnings or render failures.
  • You still need to run the final MJML validation.
// mjml-react: Runtime prop checks
// <MjmlText font-size="invalid" /> might warn in dev

🌱 Similarities: Shared Ground Across Ecosystem

While the entry points differ, all packages rely on the same rendering engine.

1. 📨 Same Output Quality

  • All produce identical HTML email code.
  • Responsive behavior is consistent across tools.
// All packages eventually call the same core logic
// Output HTML structure remains the same

2. 📦 Dependency Chain

  • mjml includes mjml-cli and mjml-core.
  • mjml-react depends on mjml for final compilation.
// Package relationships
// mjml -> mjml-core
// mjml-react -> mjml

3. 🎨 Component System

  • All support <mj-section>, <mj-column>, <mj-text>.
  • Syntax is consistent whether in string or JSX.
// Consistent component names
// <mj-text> in MJML vs <MjmlText> in React

📊 Summary: Key Differences

Featuremjmlmjml-climjml-coremjml-react
Primary UseNode.js LibraryCommand LineEngine ExtensionReact JSX
IntegrationImport in JSTerminal / ScriptCustom DevReact App
Custom TagsNo (use core)Via ConfigYes (Native)Via Components
Dynamic DataYes (Runtime)No (Static)Yes (Custom)Yes (Props)
Learning CurveLowLowestHighMedium (React)

💡 The Big Picture

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.

How to Choose: mjml vs mjml-cli vs mjml-core vs mjml-react

  • mjml:

    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.

  • mjml-cli:

    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.

  • mjml-core:

    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.

  • mjml-react:

    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.

README for mjml

Developed by


github actions

| Introduction | Installation | Usage |


Introduction

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.

Installation

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

Usage

Online

Don't want to install anything? Use the free online editor!

try it live

Applications and plugins

MJML comes with tools and plugins, check out:

For more tools, check the Community page.

Command line interface

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.

argumentdescriptiondefault value
mjml [input] -o [output]Writes the output to [output]
mjml [input] -sWrites the output to stdout
mjml [input] -s --noStdoutFileCommentWrites 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.allowIncludesEnables mj-include processing (true or false)false
mjml [input] --config.allowMixedSyntaxAllows mixing block and CSS variable syntax when sanitizeStyles is enabled (true or false)false
mjml [input] --config.beautifyBeautifies the output (true or false)true
mjml [input] --config.includePathAdds allowlisted include root(s), as a string path or JSON array of paths
mjml [input] --config.minifyMinifies the output (true or false)false
mjml [input] --config.minifyOptionsOptions for HTML minifier, use minifyCss to control CSS minificationSee mjml-cli documentation
mjml [input] --config.juicePreserveTagsPreserve some tags when inlining CSSSee mjml-cli documentation
mjml [input] --config.mjmlConfigPathPath to .mjmlconfig file for custom componentscurrent working directory
mjml [input] --config.sanitizeStylesSanitizes template variables inside CSS before minification (true or false)false
mjml [input] --config.useMjmlConfigOptionsAllows to use the options attribute from .mjmlconfig filefalse
mjml [input] --config.templateSyntaxSets custom template delimiters as JSON array ([{"prefix":"{{","suffix":"}}"}])[{"prefix":"{{","suffix":"}}"},{"prefix":"[[","suffix":"]]"}]

See mjml-cli documentation for more information about config options.

Inside Node.js

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:

optionunitdescriptiondefault value
allowMixedSyntaxbooleanAllows mixed block/value/property template syntaxes during CSS sanitizationfalse
beautifybooleanOption to beautify the HTML outputfalse
filePathstringPath of file, used for relative paths in mj-include instances.
fontsobjectDefault fonts imported in the HTML rendered by MJMLSee in index.js
ignoreIncludesbooleanOption to ignore mj-include instancestrue
includePathstring or string[]Additional allowlisted include root(s), used when ignoreIncludes is false
juicePreserveTagsobjectPreserve some tags when inlining CSS, see documentation for more info
keepCommentsbooleanOption to keep comments in the HTML outputtrue
minifybooleanOption to minify the HTML outputfalse
minifyOptionsobjectOptions for htmlnano minification (including minifyCss), see documentation for more info
mjmlConfigPathstringThe path or directory of the .mjmlconfig file (for custom components use)process.cwd()
preprocessorsarray of functionsPreprocessors applied to the xml before parsing. Input must be xml, not json. Functions must be (xml: string) => string[]
sanitizeStylesbooleanSanitizes template variables in CSS before minificationfalse
templateSyntaxarray of objectsCustom template delimiters used by sanitization ([{ prefix, suffix }])[{"prefix":"{{","suffix":"}}"},{"prefix":"[[","suffix":"]]"}]
useMjmlConfigOptionsbooleanAllows to use the options attribute from .mjmlconfig filefalse
validationLevelstringAvailable values for the validator: strict, soft, skipsoft.

Client-side (in browser)

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)
})

API

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 Slack

MJML wouldn't be as cool without its amazing community. Head over the Community Slack to meet fellow MJML'ers.