Developer Tools

JSX Email

JSX Email renders React components to email HTML. How it relates to React Email, its Outlook handling, requirements and release status, with read dates.

Olivier Bazoud
November 5, 2026
9 min read

JSX Email is a library for writing an email as a React component and rendering it to an HTML string. You build the message from its components (Html, Body, Container, Section, Button, Text, Tailwind and others) in a .jsx or .tsx file, then call render().

At a glance
Language
TypeScript, JavaScript (React)
License
MIT, free
Maintainer
Andrew Powell
Latest release
3.2.1 (2026-05-28)
Weekly npm downloads
97,446

Figures read on November 5, 2026.

Good fit
  • A JavaScript or TypeScript team that already writes React
  • A project on Node.js 22 and React 19.2 or newer
  • A CLI check of your template against email client data
  • Any sending provider: render needs no account
Not a fit
  • A project on Node.js 20 or React 18
  • A backend outside JavaScript
  • A team that wants a company as maintainer

Minimal example

A template, then the render call, built from the project's Render documentation and the ButtonProps type in its source. Button requires numeric width and height props.

TypeScript
import { Html, Body, Container, Text, Button } from 'jsx-email';
export function Welcome({ name }: { name: string }) {
return (
<Html>
<Body>
<Container>
<Text>Welcome to Acme, {name}</Text>
<Button href="https://example.com/confirm" width={200} height={44}>
Confirm email
</Button>
</Container>
</Body>
</Html>
);
}
TypeScript
import { render } from 'jsx-email';
import { Welcome } from './emails/Welcome';
const html = await render(<Welcome name="Jane" />);

render returns the HTML string. The package is jsx-email on npm, and the code is on GitHub.

Pros and cons

Pros
  • CLI check against caniemail data
  • Outlook VML fallback on Button, from props
  • Works with any provider, no account
  • Async components and <Suspense>
  • Tailwind through UnoCSS, hashed classes
Cons
  • Individual maintainer, no corporate backing
  • Much smaller adoption than React Email
  • Version 3 needs Node.js 22, React 19.2
  • No release for months while commits continue
  • Open issue: Head may blank Classic Outlook

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

How it works

The points below come from the project's documentation and source, not from our own tests.

  • Tables: Section renders a <table>. The docs say Row "wraps a table element" but is not designed to mimic a table exactly.
  • Inlining: render takes an inlineCss option that converts class names to style attributes through @jsx-email/plugin-inline. It is off by default. minify and pretty are separate options and plugins.
  • Tailwind: the Tailwind component is built on UnoCSS with Tailwind and Windi CSS presets. Its production prop replaces class names with hashed ones for Gmail's class limits.
  • Outlook conditionals: the Button source emits a VML fallback inside <!--[if mso]> and sets mso-border-alt. A Conditional component writes HTML conditional comments, with an mso prop or an expression prop (not both).
  • Plain text: the plainText option of render takes true or an object of html-to-text options.
  • Doctype: render returns a string with an XHTML 1.0 Transitional doctype.

The string is the HTML body alone. Your provider wraps it in a MIME part and adds the headers defined by the message format.

The CLI binary is email, with build, check, create, preview and help. email preview <dir> starts a preview server on http://localhost:55420, and npx create-mail my-email-project scaffolds a project. email check compares a template with caniemail compatibility data and reports unsupported features per email client. You select clients with --email-clients or check.emailClients in the config file.

Relationship to React Email

JSX Email is a separate project with its own maintainer and its own package. GitHub does not mark the repository as a fork, and neither its README nor the pages served on jsx.email use that word. A v2 migration page in the repository source, which now returns a 404 on jsx.email, says the default-export convention was inherited from React Email "when we forked". Its git history starts with React Email commits, including a chore(release): react-email v1.10.0 commit on 2023-09-05, before the first JSX Email commits on the same day. The code descends from React Email, and the authoring model is the same.

The JSX Email docs call React Email "fairly far behind jsx-email in terms of functionality". We did not verify that comparison.

Send it with an email API

render returns a string, so you can pass it to any provider. With the Nuntly TypeScript SDK:

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

The Send email from Next.js guide wires this into a server action.

Email client support

Claimed by the project
  • "Modern email clients"
  • Cross-client inconsistencies handled
  • email check against caniemail data
Not published
  • Client list
  • Client versions tested
  • Test method and results

The claim comes from the README, which says the components make templates "that work across modern email clients". We did not run tests ourselves.

Outlook on Windows needs the most care. Button draws a VML shape there from its props, and Head emits a conditional closer that an open issue says blanks Classic Outlook (see Limits and gotchas). Gmail and Outlook on Windows have essentially no dark mode support, as the project's own FAQ says, and caniemail lists no prefers-color-scheme support in either. inlineCss also removes dark mode and media queries. Before you ship, run email check and send a test to Classic Outlook and Gmail.

Limits and gotchas

  • Every Conditional closes with <![endif]/-->, and an open issue says this blanks Classic Outlook. Head adds a Conditional, so any template using <Head /> emits it, and Raw rewrites --> the same way (reproduced with 3.2.1). The reporter of issue #403 saw an empty message in Classic Outlook, and the maintainer closed the fix PR unmerged. We did not test Outlook ourselves, so check Classic Outlook, or post-process with html.replaceAll('<![endif]/-->', '<![endif]-->').
  • Nothing is inlined by default. Tailwind writes classes plus a <style> block, so styles depend on the client keeping <style>. caniemail lists Gmail as supporting it only in <head>, with a 16 KB cap, and not in Gmail mobile webmail. inlineCss: true needs @jsx-email/plugin-inline installed: without it, render logs an error and returns the HTML un-inlined.
  • inlineCss deletes media queries, dark mode and web fonts. The inline plugin removes every <style> and class after copying plain rules, so sm: and dark: classes and @font-face are gone, and a hover: class made render throw (reproduced with 3.2.1). Choose per template between inlined styles and responsive styles, and avoid pseudo-class utilities when inlining.
  • Sibling Conditional components corrupt the output. Two in the same parent lost the content of one and duplicated the other (reproduced with 3.2.1; no upstream issue found). Wrap each Conditional in its own element.
  • Outlook buttons are built from props only. Button hides the link in Outlook and draws a VML shape from width, height, backgroundColor, textColor and the border props, so style, className and Tailwind classes never reach it. A JSX child such as <strong> printed [object Object] in the VML and a missing href printed href="undefined" (reproduced with 3.2.1). Pass plain text and an href, and set the visual props.

Maintenance

Active, with a release gap to watch: the last npm release, 3.2.1, is from 2026-05-28, while commits continue (last one 2026-10-19: CI, repository, preview and website changes).

  • Install the single jsx-email package. The older @jsx-email/* packages are deprecated on npm. The repository has no GitHub Releases, only tags such as jsx-email-v3.2.1, so check npm for the current version.
  • Commits do not reach npm. A preview feature committed on 2026-06-16 is not in 3.2.1.
  • Version 3 needs Node.js 22 and React 19.2 or newer.

Compare

Sibling tools in this catalog:

  • React Email: the project whose history JSX Email starts from, maintained by Resend.
  • MJML: XML-like markup compiled to HTML, with ports outside Node.
  • mjml-react: React components that compile through MJML.
  • 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

Is JSX Email a fork of React Email?

GitHub does not mark it as a fork, and neither the README nor the pages served on jsx.email use that word, but a v2 migration page in the repository source says "when we forked". Its git history starts with React Email commits, including a chore(release): react-email v1.10.0 commit on 2023-09-05, so the code descends from React Email. It has its own maintainer, Andrew Powell, and its own package, jsx-email.

Who maintains JSX Email?

Andrew Powell, an individual (GitHub user shellscape). The README says "community-driven project with no corporate sponsorship or backing", and no email sending service runs it. It has generic integration docs for AWS SES, Loops, Nodemailer, Postmark, Resend, Plunk and SendGrid.

Does JSX Email support Tailwind?

Yes, through its Tailwind component, built on UnoCSS with Tailwind and Windi CSS presets. Its production prop swaps class names for hashed ones to stay within Gmail's class limits. The older @jsx-email/tailwind package is deprecated.

Which Node.js and React versions does JSX Email need?

Version 3 requires Node.js 22.0.0 or newer. The README says React 19.1.0 or newer, but the 3.2.1 package declares a peer range of ^19.2.0, so use React 19.2 or later.

Is JSX Email still maintained?

As read on 2026-11-05, it is active, with a gap to watch. The last commit was on 2026-10-19, but the last npm release, 3.2.1, dates from 2026-05-28, which makes it 161 days old. Commits since then are CI, repository, preview and website changes.