Software Engineering

Introducing mjmx: a custom JSX runtime for mjml

# Programming # Open Source

I wrote and published an open source library to generate mjml using JSX

In my opinion, one of the best things that happened because of React, is JSX. I know many people seem to hate on JSX, but I like it. With JSX, or more specifically TSX, one can write typesafe html, instead of relaying on libraries like handlebars, which is a great library, but it keeps your code and your variables separated. So every change in variables needs to be synced to the view-model, or worse, risk in runtime errors / blank values.

Recently, I’ve been doing some server-side rendering, and was using @kitajs/html as HTML JSX runtime. @kitajs/html is great, because it does not bring react or react-dom with it. And when I got to emails, I decided to try react-email and mjml-react. But I was disappointed in both.

First of all, I like mjml. It’s a great syntax, and it’s battle tested. react-email seems to not use mjml, but instead re-implement email rendering themselves. Now, react-email is a product by Resend, and the guys at Resend are doing email sending, so I bet they know how to render emails properly. Despite this, there is a second issue: I don’t like React, and I don’t want to bring react and react-dom just to render a couple of emails.

So I decided to try to write a custom JSX runtime for mjml. And that’s how @mjmx/core was born: mjmx Github.

It’s a custom JSX runtime that has no dependency on react or react-dom. It implements the minimum needed runtime to support all the mjml tags, as well as the allowed HTML tags. And with it, writing mjml becomes a breeze:

const Email = ({ name }: { name: string }) => (
  <mjml>
    <mj-body>
      <mj-section>
        <mj-column>
          <mj-text font-size="20px" color="#333">
            Hello {name}!
          </mj-text>
          <mj-button href="https://example.com">Click me</mj-button>
        </mj-column>
      </mj-section>
    </mj-body>
  </mjml>
);

const { html, errors } = render(<Email name="World" />);

Under the hood, render is just a proxy to mjml2html from the mjml library. This means that mjml compilation happens with each email you render. I would like to outsource the mjml compilation into a build / transpile step, but with the current approach it is not possible, unless you decide to re-implement email rendering yourself.

I hope you will find this library useful. Don’t forget to star it on GitHub, and feel free to reach out to me for questions, or open an issue / PR for suggestions and improvements.

© 2026 Dmitry Kudryavtsev | @skwee357
Unless otherwise noted, all content is generated by a human.
Content is licensed under CC BY-NC 4.0
4e1191dc