Blog/Setting Up React Email in a Monorepo: Complete Guide (2026)
Guides

Setting Up React Email in a Monorepo: Complete Guide (2026)

Learn how to set up React Email in a monorepo using npm workspaces or pnpm. Share templates across apps, organize transactional and marketing emails, and deploy from a single repo.

Soufiane E.
Soufiane E.
·
·
6 min read
·
Updated March 20, 2026
Setting Up React Email in a Monorepo: Complete Guide (2026)

Setting Up React Email in a Monorepo: Complete Guide (2026)

As your SaaS or startup grows, you'll accumulate dozens of email templates — onboarding, password resets, payment receipts, trial reminders, security alerts. Without structure, they scatter across repos and become hard to maintain.

A monorepo solves this: keep all your email templates in one place, share components across apps, and update them together.

This guide shows you how to set up React Email in a monorepo using npm workspaces (with a pnpm alternative), organized for real SaaS development.


What is a Monorepo and Why Use It for Email Templates?

A monorepo keeps multiple packages in a single repository — like having all your tools in one toolbox.

For React email templates, this means you can:



Option 1: npm Workspaces Setup

Create the project structure

mkdir email-templates-monorepo
cd email-templates-monorepo
npm init -y

Enable npm workspaces inside package.json:

{
"name": "email-templates-monorepo",
"private": true,
"workspaces": ["packages/*"]
}

Create Your Email Package

mkdir -p packages/transactional
cd packages/transactional
npm init -y

Install React Email dependencies:

npm install @react-email/components react react-dom
npm install --save-dev typescript @types/react @types/react-dom

Option 2: pnpm Workspaces Setup

If you use pnpm (recommended for monorepos due to faster installs and disk efficiency):

mkdir email-templates-monorepo
cd email-templates-monorepo
pnpm init

Create pnpm-workspace.yaml in the root:

packages:
- 'packages/*'

Then create your email package:

mkdir -p packages/transactional
cd packages/transactional
pnpm init
pnpm add @react-email/components react react-dom
pnpm add -D typescript @types/react @types/react-dom

Here is a structure that scales well for a SaaS product:

email-templates-monorepo/
├── packages/
│   ├── transactional/          # Triggered by user actions
│   │   ├── emails/
│   │   │   ├── welcome-email.tsx
│   │   │   ├── otp-email.tsx
│   │   │   ├── magic-link.tsx
│   │   │   ├── password-reset.tsx
│   │   │   ├── payment-receipt.tsx
│   │   │   ├── trial-started.tsx
│   │   │   ├── trial-ending.tsx
│   │   │   └── security-alert.tsx
│   │   └── package.json
│   ├── marketing/              # Newsletters, campaigns
│   │   ├── emails/
│   │   │   ├── newsletter.tsx
│   │   │   └── product-update.tsx
│   │   └── package.json
│   └── shared/                 # Shared components
│       ├── components/
│       │   ├── Header.tsx
│       │   ├── Footer.tsx
│       │   └── Button.tsx
│       └── package.json
└── package.json

TypeScript Configuration

Add a tsconfig.json to each package. For packages/transactional:

{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./emails"
},
"include": ["emails/**/*"]
}

And a root tsconfig.base.json to share settings across packages:

{
"compilerOptions": {
"target": "ES2020",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react-jsx"
}
}

Writing Your First Email Template

Inside packages/transactional/emails/welcome-email.tsx:

import * as React from 'react';
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
} from '@react-email/components';
interface WelcomeEmailProps {
username: string;
dashboardUrl: string;
}
export const WelcomeEmail = ({ username, dashboardUrl }: WelcomeEmailProps) => (
<Html>
<Head />
<Preview>Welcome — your account is ready!</Preview>
<Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f4f4f5' }}>
<Container style={{ margin: '0 auto', padding: '40px 20px', maxWidth: '560px' }}>
<Heading>Welcome, {username}!</Heading>
<Text>Your account is set up and ready to use.</Text>
<Section>
<Button href={dashboardUrl} style={{ backgroundColor: '#000', color: '#fff', padding: '12px 24px', borderRadius: '6px' }}>
Go to Dashboard
</Button>
</Section>
</Container>
</Body>
</Html>
);
WelcomeEmail.PreviewProps = {
username: 'Alex',
dashboardUrl: 'https://example.com/dashboard',
} as WelcomeEmailProps;
export default WelcomeEmail;

Setting Up the Preview Server

Update packages/transactional/package.json:

{
"scripts": {
"dev": "email dev --dir emails",
"export": "email export --dir emails --out dist"
}
}

Start the preview server from the monorepo root:

npm run dev -w packages/transactional
# or with pnpm:
pnpm --filter transactional dev

Open http://localhost:3000 to preview all your email templates in the browser.


Sharing Components Between Templates

Create a shared Header component in packages/shared/components/Header.tsx:

import * as React from 'react';
import { Img, Section } from '@react-email/components';
interface HeaderProps {
logoUrl: string;
logoAlt?: string;
}
export const Header = ({ logoUrl, logoAlt = 'Logo' }: HeaderProps) => (
<Section style={{ backgroundColor: '#000', padding: '20px 40px' }}>
<Img src={logoUrl} alt={logoAlt} width={120} />
</Section>
);

Add a package.json for the shared package:

{
"name": "@myapp/email-shared",
"version": "1.0.0",
"main": "./components/index.ts",
"types": "./components/index.ts"
}

Create packages/shared/components/index.ts to export everything:

export { Header } from './Header';
export { Footer } from './Footer';
export { Button } from './Button';

Then in packages/transactional/package.json, add the dependency:

{
"dependencies": {
"@myapp/email-shared": "workspace:*"
}
}

Install with npm install or pnpm install from the root to link the workspace packages.

Now import shared components in any template:

import { Header, Footer } from '@myapp/email-shared';

Using with Turborepo (Optional)

If you already use Turborepo, add your email package to the pipeline in turbo.json:

{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"export": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}

Run all email exports in parallel with the rest of your build:

turbo run export

Testing Your Email Templates

With React Email, there are two levels of testing:

1. Visual testing (always do this)

Run npm run dev in the transactional package and check each template at http://localhost:3000. Preview in desktop and mobile views.

2. Snapshot testing with Vitest

Add Vitest to your transactional package:

npm install --save-dev vitest @vitejs/plugin-react

Write a simple snapshot test:

// emails/__tests__/welcome-email.test.tsx
import { render } from '@react-email/render';
import { describe, expect, it } from 'vitest';
import { WelcomeEmail } from '../welcome-email';
describe('WelcomeEmail', () => {
it('renders without errors', async () => {
const html = await render(
<WelcomeEmail username="Alex" dashboardUrl="https://example.com" />
);
expect(html).toContain('Alex');
expect(html).toContain('https://example.com');
});
});

Best Practices


What's Next?

Now that your monorepo is set up:

  1. Send Emails: Learn how to send React Email templates with Resend in production
  2. Get Inspiration: Browse React email template examples you can adapt
  3. Full SaaS email guide: Read about all the email templates a SaaS app needs
  4. Save Time: Get 12 production-ready templates already organized, typed, and tested — drop them straight into your monorepo