We’ve created this guide to help new contributors understand and navigate the React Email codebase.

Top-level directories

After cloning the React Email repository you will see a few root-level directories. Here’s a brief overview of each:

DirectoryDescription

apps

Here you can find all of the apps related to our online presence, like:

benchmarks

We make benchmarks from version-to-version to demonstrate data-observable performance gains with metrics like p99 and p75.

For example, see the Improved Performance for Tailwind Emails benchmark.

packages

Most contributions will be made to the packages in this directory.

This directory contains all our published NPM packages. Each subdirectory is a single component published as its own package, with the exception of a few packages that serve as shared configuration.

Feel free to open a discussion if you have suggestions on how to better structure these packages to make them more manageable and approachable.

Multiple packages

The react-email repository is a pnpm monorepo, which means it contains multiple packages.

Because we use pnpm, you will need to use pnpm to install and run each package. If you do not have pnpm installed, we recommend you install it using corepack:

corepack enable
corepack prepare pnpm@latest --activate

Currently, we have the following packages:

Most of these packages are very small and can be easily understood by reading the code, so feel free to explore.

Turborepo

We encourage using turborepo to manage the packages.

It’s often helpful to install Turborepo globally to make it easier to run commands in any of the repositories. With a global installation, running turbo build in any of the packages will build both the package you are on as well as the dependent packages. The global installation handles version mismatching as well.

The React Email CLI

The CLI (i.e. packages/react-email) is key to the best development experience with react.email, but it is complex. Here is a brief overview of the CLI.

The CLI includes two components:

  • A Next app for the email dev and email build commands
  • A commander.js CLI

In the NextJS app, we include a src/cli directory that is not published but is compiled into a root cli directory. This structure provides a good developer experience as we can both share certain functions and communicate between the CLI components.

We trigger rebuilds of email templates after they have been saved using the chokidar package alongside the socket.io package to detect file changes and send a message to the server to trigger a rebuild.

Testing

For testing, we use vitest. We prefer to define globals and run tests under the happy-dom environment.

We do not strictly enforce testing coverage, but encourage it.

For help testing, see our Development workflow guide.

The @react-email/render package’s renderAsync does a fair bit of magic to simulate edge and other environments that are not supported by happy-dom. For this use case, we override the environment on a per-file basis for its tests

Linting

We use biomejs for linting and formatting. Both the linting and formatting are ensured by our GitHub CI so make sure you lint and format your code (pnpm lint:fix) before opening a PR or asking for a review on it.

For help linting and formatting, see our Development workflow guide on linting.

Building

We use tsup to build most packages. (The only exception for this is the @react-email/tailwind package which currently uses vite due to a few issues with tsup and tailwindcss’s bundling.) For help building packages, see our Development workflow guide.

Building in each package will run tsup with a few settings, typically src/index.ts --format esm,cjs --dts --external react. Tsup handles building both ESM and CJS versions along with the type definitions exported from the entry point, src/index.ts, without bundling react, which can cause issues.

Why build before publishing?

We build most of the packages before publishing for a few reasons:

  1. All the exported types can be imported from the same place the JavaScript is imported
  2. We have proper CommonJS and ES Modules support
  3. Code that isn’t exported is not published or downloaded