Developer Tools

mjml-react

mjml-react turns React components into MJML, which compiles to email HTML. Maintenance-only. Example, limits, sending the HTML and the Wix package.

Olivier Bazoud
November 5, 2026
8 min read

mjml-react (npm package @faire/mjml-react) lets you write an MJML email as React components. You compose MjmlSection, MjmlColumn and MjmlButton in JSX or TSX, the library turns the tree into an MJML string, and the mjml package turns that string into email HTML. The library produces no HTML itself.

At a glance
Language
TypeScript (React)
License
MIT, free
Maintainer
Faire
Latest release
4.0.1 (2026-06-01), maintenance-only
Weekly npm downloads
144,496

Figures read on November 5, 2026.

Good fit
  • A React team that wants MJML's layout components
  • A Node.js backend that can keep mjml as a peer dependency
  • Any sending provider: render needs no account
  • A move off the abandoned Wix mjml-react package
Not a fit
  • A backend outside JavaScript
  • A setup that needs plain text, a preview server or a CLI built in
  • A project that wants fewer packages in the render path

Minimal example

The package exports an async render(email, options) at @faire/mjml-react/utils/render. It converts the React tree to MJML, compiles it with mjml, and resolves to { html, errors }.

TypeScript
import { Mjml, MjmlBody, MjmlSection, MjmlColumn, MjmlButton } from '@faire/mjml-react';
import { render } from '@faire/mjml-react/utils/render';
const { html, errors } = await render(
<Mjml>
<MjmlBody width={500}>
<MjmlSection>
<MjmlColumn>
<MjmlButton padding="20px" backgroundColor="#346DB7" href="https://example.com/">
I like it!
</MjmlButton>
</MjmlColumn>
</MjmlSection>
</MjmlBody>
</Mjml>,
{ validationLevel: 'soft' },
);

The template is shortened from the project's README example. The README wires the same steps by hand: mjml2html(renderToMjml(email)). Install @faire/mjml-react, mjml, react and react-dom: mjml and React are peer dependencies.

Pros and cons

Pros
  • Components generated from MJML definitions
  • Typed props, though units go unchecked
  • Peer range covers MJML 5 and React 19
  • Renders with mjml alone, no account
  • Backwards compatible with the Wix package on v2, per the README
Cons
  • Two packages in the render path
  • No plain text, preview server or CLI
  • Invalid MJML throws by default
  • Numeric props silently dropped
  • Few commits, maintenance-only

The details behind each point are in Limits and gotchas and Maintenance.

How it works

render calls renderToMjml(element), which walks your React tree and writes an MJML string, then hands that string to mjml2html. Since v3 the components come from a script that reads the MJML component definitions, which the project intends to track every component of the latest MJML version. Every component prop is typed in TypeScript.

After that step MJML does the work: the table layout and the Outlook markup come from mjml2html, not from this library. MJML inlines only the CSS of <MjmlStyle inline>, and all other CSS stays in a <style> block in the <head>. This library's repository does not document the output, so read the MJML page for it. For what your HTML becomes on the wire, see MIME and the message format.

The README lists the MJML options the library sets by default in render: keepComments: false, beautify: false, minify: false, a minifyOptions block (collapseWhitespace, minifyCss, removeComments: "safe", removeEmptyAttributes) and validationLevel: 'strict'. You override them through the second argument of render. If you call mjml2html(renderToMjml(email)) yourself, as the README's hand-written helper does, those defaults do not apply and MJML's own defaults do.

Send it with an email API

The render step gives you an HTML string. Pass it to the Nuntly TypeScript SDK.

TypeScript
import { Nuntly } from '@nuntly/sdk';
import { render } from '@faire/mjml-react/utils/render';
const nuntly = new Nuntly({ apiKey: process.env.NUNTLY_API_KEY! });
const { html } = await render(<Welcome />);
await nuntly.emails.send({
from: 'Acme <hello@example.com>',
to: 'jane@example.org',
subject: 'Welcome to Acme',
html,
});

Welcome stands for your own template component. The Send your first email guide covers the client setup and the response.

Email client support

Claimed by the project
  • Email rendering issues kept to a minimum
  • Output that works on mobile
Not published
  • Client list
  • Client versions tested
  • Dark mode
  • Own VML code for Outlook

The claim comes from the README, which names no email client. Client support is MJML's, so the MJML page covers what MJML says. We did not run the tests.

The only Outlook code in this library is MjmlConditionalComment, which writes an <!--[if gte mso 9]> block and takes a string. The tables Outlook on Windows needs come from MJML. There is no dark mode support: a prefers-color-scheme rule you write in <MjmlStyle> reaches the <style> block unchanged, but caniemail lists it as unsupported in Gmail, Outlook on Windows and Yahoo! Mail. MjmlFont loads fonts through <link> and @import, which caniemail lists as unsupported in Gmail. Before you ship, send a test to Outlook on Windows and Gmail, and look up any CSS property you depend on in caniemail.

Limits and gotchas

  • render throws on invalid MJML instead of filling errors. The default validation level is 'strict', so await render(...) rejects with a ValidationError and the returned errors array stays empty. Wrap the call in try/catch, or pass { validationLevel: 'soft' } and read errors, for example when attributes hold template placeholders such as {{brand}}, which fail strict validation.
  • Numeric props are silently wrong or dropped. A number becomes px only for a fixed list of props such as width, padding and lineHeight. About 19 other props that TypeScript accepts as numbers, such as maxHeight, gap, iconSize and border, lose the attribute with no warning, and lineHeight={1.5} becomes 1.5px. Pass strings with units, for example "50%" or "100px" (issue #87, open since 2023).
  • MjmlConditionalComment only takes a string. A JSX child renders <!--[if gte mso 9]>[object Object]<![endif]-->, which breaks the Outlook-only markup you meant to add. Write the Outlook markup as a string. The fixConditionalComment util shown in the README was removed in v3.
  • v4 changes two things at once. render became async and the mjml peer moved from 4 to 5, so an upgrade from 3.x needs await at every call site and a new mjml major in the same step. Code and tutorials written for v3 fail or compile against the wrong MJML.
  • Native Node ESM needs the .js suffix on subpaths. The package has no exports map, so in an ESM project @faire/mjml-react/utils/render fails with ERR_MODULE_NOT_FOUND. Import @faire/mjml-react/utils/render.js (and extensions/index.js) instead. Bundlers resolve the short form, which the examples above use.

Maintenance

Maintenance-only: the last release, v4.0.1, is from 2026-06-01, and releases before it were months apart (nothing between 2025-09-02 and 2026-04-30).

  • Install @faire/mjml-react, not mjml-react. The Wix package is abandoned (last release 2022-05-20) but still draws about 40,000 weekly downloads, so old tutorials lead to it. Faire's v2 line is backwards compatible with it, so switch first and upgrade later.
  • Expect an async render from v4. It moved to MJML 5 and made render asynchronous.
  • Expect slow review. One maintainer account answers, and three outside PRs opened between 2026-05-12 and 2026-08-31 have no reply.

Compare

mjml-react is one way to write MJML. These siblings cover the other ways to author email.

  • MJML: the markup language and compiler that mjml-react calls, if you would rather write MJML directly.
  • React Email: React components that render to email HTML without MJML.
  • React Email vs MJML: components against markup, with the same template in both.
  • JSX Email: React components too, with its own maintainer.
  • Maizzle: Vue single-file components with Tailwind CSS.
  • svelte-email: Svelte components.
  • @unitpost/email: JSX or TSX, or the same tags as a string with no build step.

The email frameworks hub lists every tool with its status.

FAQ

What is the difference between mjml-react and @faire/mjml-react?

mjml-react is the original package from Wix, abandoned since 2022. @faire/mjml-react is the fork Faire maintains. Faire's v2 line is described as backwards compatible with the Wix package, so moving is an install change first.

Is mjml-react still maintained?

As read on 2026-11-05, @faire/mjml-react is maintenance-only. The latest release is v4.0.1 from 2026-06-01, which followed the MJML 5 upgrade in v4.0.0. Commits are infrequent, and the 2026 ones are mostly dependency and CI work.

Does mjml-react produce the HTML itself?

No. It converts your React tree into an MJML string with renderToMjml. render then passes that string to mjml2html from the mjml package, which returns html and errors. The table layout and the Outlook markup come from MJML, which inlines only the CSS of <MjmlStyle inline>.

Does mjml-react work with React 19?

The peer dependency range in @faire/mjml-react 4.0.1 covers React 16 to 19 and mjml ^5.0.0, as read on npm on 2026-11-05.

Can I use mjml-react in the browser?

The README offers mjml-browser as an alternative to mjml, but its Limitations section says the libraries are meant to be run inside a node. The README does not reconcile the two statements, so treat server-side rendering as the supported path.