# jsx-email renders React components into email HTML

> jsx-email is an MIT licensed TypeScript library that lets you build responsive email templates with React components instead of table soup, handling the cross-client inconsistencies that make email rendering hard. Version 3.0.0 modernises the runtime, the package exports, the preview app and the CLI, and requires Node 22 and React 19.1 or newer.

**shellscape/jsx-email** — Build emails with a delightful DX

- Repository: https://github.com/shellscape/jsx-email
- Website: https://jsx.email
- Stars: 1,267 · Forks: 54
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/shellscape-jsx-email

## The pitch is that email stops being a separate medium

jsx-email describes itself as build and send emails using React and TypeScript, and the README is direct about the problem it solves. It provides a set of React components and helpers for building delightful, responsive email templates that work across modern email clients. The components handle compatibility and client inconsistencies so designers and developers can focus on building impactful templates instead of fighting email rendering quirks.

That last clause is the whole product. Email HTML is not browser HTML. The rendering engines are a decade of different implementations, some with no support for modern CSS, and the practical consequence is that a layout that works in a browser has to be expressed in a way that survives all of them. Every team that sends email ends up with a private library of workarounds, and that library is exactly the thing this package is.

The approach is a component model rather than a template language. You write a React tree, the components render to the constrained HTML that email clients need, and the output is a string that any provider with an API for sending email as a string can take.

The feature list names the parts that matter. Email Client Compatibility Checking, which is a CLI capability that tells you how a template will render across clients before you send it. Exclusive Components, meaning components that do not exist in plain React and exist precisely because email needs them. Handles cross-client inconsistencies for you. Optional Configuration Files. Plugins. Crazy fast Tailwind support, which matters because utility classes in email need to be compiled and inlined, and that compilation is slow. Support for Suspense and async within Components, which is how an async data fetch inside a template component is resolved before rendering. A smooth and simple Preview Server. Wonderful Command Line tools. And works with monorepos out of the box, with no exhaustive setup needed.

The last two claims, monorepos and the preview server, are the ones that decide whether it works in your organisation.

## Version 3 changed the ground under you

The version section is short and it is the first thing a user should read.

Version 3 modernizes the runtime, package exports, preview app, scaffold, plugins, and CLI. It requires Node.js 22.0.0 or newer and React 19.1.0 or newer. Please read the v3 Migration Guide before upgrading existing projects.

And then there is a version of the README for people who are not ready. To browse the source code and documentation markdown for v2.8.4, use the jsx-email-v2.8.4 tag.

So the project has been through two significant rewrites, and the previous major line is preserved as a tag rather than as a branch. That is a reasonable way to keep the old documentation readable, and it is also a signal that the migration is not trivial enough to be waved through.

The runtime requirement is the one that bites hardest. React 19.1 or newer is a recent React, and Node 22 is a recent LTS. A team on React 18 cannot use the current line, and the feature list mentions Support for Suspense and async within Components, which is the kind of thing that needs a React with concurrent rendering. So the v3 line is not merely a refactor, it is a project that assumes a modern React.

The repository structure shows how big the project has become. There are apps/ for the applications including the preview and the docs site, packages/ for the published packages, a shared/ directory with a bootstrap.sh script, a recipes/ directory, a scripts/ directory, test/ and a docs/ directory that is not in the top level listing but is referenced by the documentation links, and a .moon/ directory with a moon.yml file.

That is a monorepo, and the tool is moonrepo, with @moonrepo/cli at 1.41.7 in the dev dependencies.

## The toolchain is a modern TypeScript stack with a Rust lint step

The root package.json is worth reading because it shows what a well maintained TypeScript monorepo looks like in 2026, and the choices are not the defaults.

```json
"devDependencies": {
  "@swc/core": "^1.3.91",
  "@swc/helpers": "^0.5.2",
  "happy-dom": "^20.1.0",
  "oxfmt": "^0.47.0",
  "oxlint": "^1.62.0",
  "vitest": "^4.0.17",
  "typescript": "catalog:"
}
```

Several of these are worth a sentence each.

oxlint and oxfmt replace ESLint and Prettier, and they are written in Rust, which is why they are fast enough to sit in a lint-staged hook that runs on every commit. The lint-staged configuration runs oxlint with --fix on TypeScript, JavaScript and MDX files, and then oxfmt --write on those files, on package and tsconfig JSON, on the workspace and GitHub Actions YAML, and on the Markdown files including READMEs and CHANGELOGs. So the formatter handles prose as well as code, which is a small but real quality decision for a project with a lot of documentation.

@swc/core is the compiler used instead of Babel or the TypeScript compiler for the build, and happy-dom is the DOM implementation the tests run against, which is how a React component library can be tested without a browser.

The TypeScript version comes from catalog, which is a pnpm workspace catalog, a mechanism that lets many packages share one version range, and the pnpm overrides block pins @types/react, @types/react-dom, react and react-dom, plus react-router-dom at 6.16.0, so a transitive dependency cannot pull a different React into a package that expects a specific one.

The package manager is pnpm at 11.1.1, the engines field requires node >=22.0.0, and the scripts are minimal at the root because moonrepo runs the real task graph. The bootstrap script runs shared/bootstrap.sh and the prepare script installs the husky hooks.

## One render output, many providers

Email built and rendered with JSX email can be used with any email provider that provides an API for sending email as a string. The list it gives is AWS SES, Loops, Nodemailer, Postmark, Resend, Plunk and SendGrid, and the documentation has an email providers page with example usage.

That list is the point. The library's job ends when it has a string, and the sending is somebody else's problem. That separation is what keeps the project from becoming a wrapper around one vendor, and the README makes the same claim in the feature list, that there is no vendor lock-in for tools because jsx-email uses only generic components and tools.

The word generic is doing quiet work. It means the output is HTML with tables and inline styles, the kind of markup every client can render, rather than anything requiring a specific provider's rendering engine. A provider that accepts a string can send it, and a team can move between providers without touching the templates.

For a project, the practical workflow is the create-mail scaffold.

```sh
npx create-mail my-email-project
```

And the package has its own documentation site at jsx.email with an introduction page covering the components, the props and the usage, plus a specific page for the CLI's email client compatibility check.

The contribution note is also worth reading, because it tells you what kind of project this is. The project is community-driven with no corporate sponsorship or backing, the maintainers and users keep this project going, and we, the maintainers, use JSX email daily.

The repository carries a .moon/ directory and moon.yml for the task graph, an AGENTS.md, a .husky/ directory for the hooks, an .oxfmtrc.json and .oxlintrc.json for the Rust tools, a .zed/ directory for the Zed editor configuration, a .nvmrc for the Node version, a .npmrc, and a pnpm-workspace.yaml. It also has a LICENSE.md and the MIT licence, and the README links to the Contribution Guide in CONTRIBUTING.md.

## Where jsx-email is the wrong choice

The first wrong case is a project with no React in its stack. The premise is that you have a component library and want your email templates to be components too, so a design token change or a shared header component propagates into the email. If your backend is Python or Go and your email is a handful of templates, pulling in React and a rendering toolchain to produce the same string is a lot of machinery.

The second wrong case is a team that sends simple transactional email and values minimal dependencies. A password reset and an order confirmation do not need a compatibility layer, a preview server, a client compatibility checker and a plugin system. The library is good at the hard cases, and the hard case is a marketing email with a grid layout and a dozen media queries, not a one-paragraph message.

The third wrong case is a project with a strict render-time budget for email generation. Because the components support async and Suspense, generating a template means resolving the asynchronous work before the HTML can be produced. For a service that renders an email on every order, that is a real cost to consider, and a static template is faster.

The fourth wrong case is a team that cannot move to React 19. The v3 line requires React 19.1.0 or newer and Node 22. The older tag, v2.8.4, is available to read but the project is clearly moving forward, so a team on React 18 is choosing between the old line and a fork.

## How it differs from writing email HTML by hand

The comparison worth making is with the standard approach, which is a template engine and a table of inline styles, and the difference in approach is where the client quirks are handled.

The hand-written approach is a template plus a stylesheet that is mostly inlined, and the template is a grid of tables because that is the only layout primitive every client supports. The workarounds are invisible in the output but dominant in the source. Every flex container needs a table fallback, every image needs a fixed width, every media query needs a duplicate of the desktop rule with different inline styles, and Outlook needs its own conditional comments because its rendering engine is a different one entirely.

jsx-email puts those workarounds in the components. The developer writes a Section and a Column and a Text, the components emit the table structure and the inline styles, and the developer never has to know which client does what. That is the same argument as a CSS framework, applied to a medium where CSS barely works, and it is the argument that makes the library worth its dependency.

The second difference is the type system. A React template is type checked, so a prop that does not exist is a compile error rather than an element that renders as nothing. A string template has no such check, and a misspelled attribute is silently dropped by the renderer.

The third difference is reuse. A shared header component is a component, so a change to the company logo or the footer legal text is one edit in the codebase rather than a find and replace across a directory of templates. For a team with many transactional emails that is the difference that matters, and it is why the library describes itself as focusing on maintenance.

The fourth difference is the tooling around it. The preview server gives a local render, the compatibility check tells you what a client will do with the template before a customer sees it, and the Tailwind support means a team already using utility classes can use the same class names in email. None of that is possible with a hand-written template and an email testing service, because in that setup the template and the preview are separate worlds.

## Conclusion

jsx-email fits a React team that maintains transactional email, because a template becomes a component tree that can be shared with the application, previewed locally and type checked, and the compatibility handling stops each new engineer rediscovering why a flex container renders differently in Outlook. It does not fit a team that sends a handful of simple templates and has no React in the stack, and it does not fit a project that needs its email output to be checked into a static build. Before adopting it, read the v3 migration guide, since the release requires Node 22 and React 19.1 and changed the runtime, the exports, the preview app and the CLI, and note that the package is published without GitHub releases.

## FAQ

### What is jsx-email and what does it do?

It is an MIT licensed TypeScript library for building responsive email templates with React and TypeScript. It provides React components and helpers that handle client compatibility and rendering inconsistencies across modern email clients, so developers can build templates without fighting email rendering quirks.

### What are the requirements for jsx-email version 3?

Node.js 22.0.0 or newer and React 19.1.0 or newer. The README asks you to read the v3 migration guide before upgrading, because version 3 modernizes the runtime, the package exports, the preview app, the scaffold, the plugins and the CLI. The previous line is browsable as the jsx-email-v2.8.4 tag.

### Which email providers work with jsx-email?

Any provider with an API for sending email as a string, and the README lists AWS SES, Loops, Nodemailer, Postmark, Resend, Plunk and SendGrid. The rendered output is generic HTML, so the project claims no vendor lock-in for tools.

### How do I start a new jsx-email project?

Use the create-mail scaffold with npx create-mail my-email-project. The package also ships a preview server, a CLI with an email client compatibility check, plugin support and optional configuration files, and it works with monorepos without exhaustive setup.

### How is the jsx-email repository built and tested?

It is a pnpm workspace driven by moonrepo, with the pnpm packageManager pinned to 11.1.1 and node >=22.0.0 required. The toolchain uses oxlint and oxfmt for linting and formatting through lint-staged, @swc/core for compilation, happy-dom for the DOM in tests, vitest for the test runs, and the TypeScript version comes from a pnpm catalog with overrides pinning React and its types.

## Sources

- [Issues](https://github.com/shellscape/jsx-email/issues)
- [License: MIT](https://github.com/shellscape/jsx-email/blob/main/LICENSE)
- [Project website](https://jsx.email)
- [README](https://github.com/shellscape/jsx-email/blob/main/README.md)
- [shellscape/jsx-email on GitHub](https://github.com/shellscape/jsx-email)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/shellscape-jsx-email
