React Email
Build beautiful emails using React components with TypeScript support
Build type-safe, component-based email templates using React Email and send them with Keplars Mail Service.
What is React Email? A collection of high-quality, unstyled React components for creating beautiful emails. Built by Resend.
Why React Email?
- Component-Based: Reusable email components (Header, Footer, Button)
- Type-Safe: Full TypeScript support with prop validation
- Live Preview: Dev server to preview emails in real-time
- Testable: Unit test your email templates
- Version Control: Git-friendly, works with your existing workflow
- Modern DX: ESLint, Prettier, and hot reload support
Installation
Install Dependencies
npm install react-email @react-email/componentsInitialize React Email (Optional)
npx email initThis creates an emails/ directory with example templates.
Quick Start
Create Your First Template
Create emails/welcome.tsx:
import {
Body,
Button,
Container,
Head,
Html,
Preview,
Section,
Text,
} from '@react-email/components';
import * as React from 'react';
interface WelcomeEmailProps {
userName: string;
dashboardUrl?: string;
}
export const WelcomeEmail = ({
userName,
dashboardUrl = 'https://app.keplars.com',
}: WelcomeEmailProps) => {
return (
<Html>
<Head />
<Preview>Welcome to Keplars - Get started now</Preview>
<Body style={main}>
<Container style={container}>
<Section style={section}>
<Text style={heading}>Welcome {userName}!</Text>
<Text style={text}>
Thank you for joining Keplars Mail Service. We're excited to help you
send beautiful emails.
</Text>
<Button style={button} href={dashboardUrl}>
Go to Dashboard
</Button>
</Section>
</Container>
</Body>
</Html>
);
};
export default WelcomeEmail;
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
marginBottom: '64px',
};
const section = {
padding: '0 48px',
};
const heading = {
fontSize: '32px',
lineHeight: '1.3',
fontWeight: '700',
color: '#484848',
};
const text = {
fontSize: '16px',
lineHeight: '26px',
color: '#484848',
};
const button = {
backgroundColor: '#5469d4',
borderRadius: '4px',
color: '#fff',
fontSize: '16px',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'block',
width: '100%',
padding: '12px',
};Render and Send
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
async function sendWelcomeEmail(userEmail: string, userName: string) {
const html = await render(<WelcomeEmail userName={userName} />);
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: [userEmail],
subject: `Welcome ${userName}!`,
body: html,
}),
});
return response.json();
}
await sendWelcomeEmail('[email protected]', 'John Doe');Complete Examples
2FA Code Email (Instant Priority)
import { Body, Container, Head, Html, Section, Text } from '@react-email/components';
import * as React from 'react';
interface TwoFactorEmailProps {
code: string;
expiresIn?: number;
}
export const TwoFactorEmail = ({ code, expiresIn = 10 }: TwoFactorEmailProps) => {
return (
<Html>
<Head />
<Body style={main}>
<Container style={container}>
<Section style={section}>
<Text style={heading}>Your Verification Code</Text>
<Text style={text}>
Use this code to complete your authentication:
</Text>
<Text style={codeStyle}>{code}</Text>
<Text style={subtext}>
This code expires in {expiresIn} minutes.
</Text>
</Section>
</Container>
</Body>
</Html>
);
};
export default TwoFactorEmail;
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
};
const section = {
padding: '0 48px',
};
const heading = {
fontSize: '24px',
fontWeight: '700',
color: '#484848',
};
const text = {
fontSize: '16px',
lineHeight: '26px',
color: '#484848',
};
const codeStyle = {
fontSize: '32px',
fontWeight: '700',
letterSpacing: '8px',
textAlign: 'center' as const,
color: '#5469d4',
backgroundColor: '#f4f4f4',
padding: '16px',
borderRadius: '4px',
margin: '24px 0',
};
const subtext = {
fontSize: '14px',
color: '#666',
textAlign: 'center' as const,
};Send with instant priority:
import { render } from '@react-email/render';
import { TwoFactorEmail } from './emails/two-factor';
async function send2FACode(userEmail: string, code: string) {
const html = await render(<TwoFactorEmail code={code} />);
await fetch('https://api.keplars.com/api/v1/send-email/instant', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: [userEmail],
subject: 'Your verification code',
body: html,
}),
});
}Order Confirmation
import {
Body,
Button,
Column,
Container,
Head,
Html,
Row,
Section,
Text,
} from '@react-email/components';
import * as React from 'react';
interface OrderConfirmationProps {
customerName: string;
orderNumber: string;
orderDate: string;
items: Array<{ name: string; quantity: number; price: number }>;
totalAmount: number;
trackingUrl?: string;
}
export const OrderConfirmation = ({
customerName,
orderNumber,
orderDate,
items,
totalAmount,
trackingUrl,
}: OrderConfirmationProps) => {
return (
<Html>
<Head />
<Body style={main}>
<Container style={container}>
<Section>
<Text style={heading}>Order Confirmed! 🎉</Text>
<Text style={text}>Hi {customerName},</Text>
<Text style={text}>
Your order has been confirmed and will be shipped soon.
</Text>
<Section style={infoSection}>
<Text style={label}>Order Number</Text>
<Text style={value}>{orderNumber}</Text>
<Text style={label}>Order Date</Text>
<Text style={value}>{orderDate}</Text>
</Section>
<Section style={itemsSection}>
<Text style={subheading}>Order Items</Text>
{items.map((item, index) => (
<Row key={index} style={itemRow}>
<Column>
<Text style={itemName}>{item.name}</Text>
<Text style={itemQuantity}>Qty: {item.quantity}</Text>
</Column>
<Column align="right">
<Text style={itemPrice}>${item.price.toFixed(2)}</Text>
</Column>
</Row>
))}
</Section>
<Section style={totalSection}>
<Text style={totalLabel}>Total</Text>
<Text style={totalAmount}>${totalAmount.toFixed(2)}</Text>
</Section>
{trackingUrl && (
<Button style={button} href={trackingUrl}>
Track Your Order
</Button>
)}
</Section>
</Container>
</Body>
</Html>
);
};
export default OrderConfirmation;
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
maxWidth: '600px',
};
const heading = {
fontSize: '28px',
fontWeight: '700',
color: '#484848',
padding: '0 48px',
};
const subheading = {
fontSize: '20px',
fontWeight: '600',
color: '#484848',
marginTop: '32px',
};
const text = {
fontSize: '16px',
lineHeight: '26px',
color: '#484848',
padding: '0 48px',
};
const infoSection = {
padding: '0 48px',
marginTop: '24px',
backgroundColor: '#f4f4f4',
borderRadius: '4px',
padding: '16px 48px',
};
const label = {
fontSize: '12px',
color: '#666',
fontWeight: '600',
marginBottom: '4px',
marginTop: '16px',
};
const value = {
fontSize: '16px',
color: '#484848',
marginTop: '0',
};
const itemsSection = {
padding: '0 48px',
marginTop: '32px',
};
const itemRow = {
borderBottom: '1px solid #e6e6e6',
paddingBottom: '16px',
marginBottom: '16px',
};
const itemName = {
fontSize: '16px',
color: '#484848',
margin: '0',
};
const itemQuantity = {
fontSize: '14px',
color: '#666',
margin: '4px 0 0',
};
const itemPrice = {
fontSize: '16px',
fontWeight: '600',
color: '#484848',
margin: '0',
};
const totalSection = {
padding: '24px 48px',
borderTop: '2px solid #484848',
marginTop: '24px',
};
const totalLabel = {
fontSize: '18px',
fontWeight: '600',
color: '#484848',
display: 'inline-block',
marginRight: '16px',
};
const totalAmount = {
fontSize: '24px',
fontWeight: '700',
color: '#5469d4',
display: 'inline-block',
};
const button = {
backgroundColor: '#5469d4',
borderRadius: '4px',
color: '#fff',
fontSize: '16px',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'block',
width: '100%',
padding: '12px',
marginTop: '32px',
};Express.js Integration
import express from 'express';
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
import { OrderConfirmation } from './emails/order-confirmation';
const app = express();
app.use(express.json());
const KEPLERS_API_KEY = process.env.KEPLERS_API_KEY;
async function sendEmail(to: string, subject: string, html: string) {
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: [to], subject, body: html }),
});
if (!response.ok) {
throw new Error(`Failed to send email: ${response.statusText}`);
}
return response.json();
}
app.post('/api/send-welcome', async (req, res) => {
try {
const { email, name } = req.body;
const html = await render(<WelcomeEmail userName={name} />);
const result = await sendEmail(email, `Welcome ${name}!`, html);
res.json({ success: true, data: result });
} catch (error) {
console.error('Failed to send welcome email:', error);
res.status(500).json({ error: 'Failed to send email' });
}
});
app.post('/api/send-order-confirmation', async (req, res) => {
try {
const { email, customerName, order } = req.body;
const html = await render(
<OrderConfirmation
customerName={customerName}
orderNumber={order.number}
orderDate={order.date}
items={order.items}
totalAmount={order.total}
trackingUrl={order.trackingUrl}
/>
);
const result = await sendEmail(
email,
`Order Confirmation - ${order.number}`,
html
);
res.json({ success: true, data: result });
} catch (error) {
console.error('Failed to send order confirmation:', error);
res.status(500).json({ error: 'Failed to send email' });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Next.js Integration
API Route (App Router)
Create app/api/send-email/route.ts:
import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/welcome';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
try {
const { email, name } = await request.json();
const html = await render(<WelcomeEmail userName={name} />);
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: [email],
subject: `Welcome ${name}!`,
body: html,
}),
});
if (!response.ok) {
throw new Error('Failed to send email');
}
const result = await response.json();
return NextResponse.json({ success: true, data: result });
} catch (error) {
console.error('Error sending email:', error);
return NextResponse.json(
{ error: 'Failed to send email' },
{ status: 500 }
);
}
}Server Action
Create app/actions/send-email.ts:
'use server';
import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/welcome';
export async function sendWelcomeEmail(email: string, name: string) {
const html = await render(<WelcomeEmail userName={name} />);
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: [email],
subject: `Welcome ${name}!`,
body: html,
}),
});
if (!response.ok) {
throw new Error('Failed to send email');
}
return response.json();
}Use in a client component:
'use client';
import { sendWelcomeEmail } from './actions/send-email';
export function WelcomeForm() {
async function handleSubmit(formData: FormData) {
const email = formData.get('email') as string;
const name = formData.get('name') as string;
await sendWelcomeEmail(email, name);
}
return (
<form action={handleSubmit}>
<input type="email" name="email" required />
<input type="text" name="name" required />
<button type="submit">Send Welcome Email</button>
</form>
);
}Available Components
React Email provides many pre-built components:
Layout Components
<Html>- Root HTML element<Head>- Document head<Body>- Document body<Container>- Centered container (max-width: 600px)<Section>- Generic section wrapper<Row>/<Column>- Grid layout
Content Components
<Text>- Paragraph text<Heading>- Headings (h1-h6)<Link>- Styled links<Button>- Call-to-action buttons<Img>- Optimized images<Hr>- Horizontal divider<Code>- Inline or block code<Preview>- Email preview text (inbox preview)
Example Usage
import {
Button,
Code,
Column,
Container,
Heading,
Hr,
Img,
Link,
Preview,
Row,
Section,
Text,
} from '@react-email/components';
export const CompleteExample = () => (
<Html>
<Head />
<Preview>Complete example of React Email components</Preview>
<Body>
<Container>
<Img src="https://example.com/logo.png" alt="Logo" width="200" />
<Heading>Welcome to React Email</Heading>
<Text>This email showcases various components.</Text>
<Hr />
<Section>
<Row>
<Column>Left content</Column>
<Column>Right content</Column>
</Row>
</Section>
<Code>const code = "highlighted code";</Code>
<Button href="https://example.com">Click Me</Button>
<Text>
Visit our <Link href="https://example.com">website</Link>
</Text>
</Container>
</Body>
</Html>
);Development Workflow
Preview Emails Locally
Start the React Email dev server:
npm run email devThis starts a local server at http://localhost:3000 where you can:
- Preview all your email templates
- Test with different props
- See changes in real-time
- Test responsive design
Project Structure
your-project/
├── emails/
│ ├── welcome.tsx
│ ├── two-factor.tsx
│ ├── order-confirmation.tsx
│ ├── password-reset.tsx
│ └── components/
│ ├── header.tsx
│ ├── footer.tsx
│ └── button.tsx
├── src/
│ └── lib/
│ └── email.ts # Email sending utility
└── package.jsonReusable Components
Create shared email components:
// emails/components/header.tsx
import { Img, Section, Text } from '@react-email/components';
export const EmailHeader = () => (
<Section style={header}>
<Img
src="https://your-domain.com/logo.png"
alt="Company Logo"
width="150"
/>
<Text style={tagline}>Your Company Tagline</Text>
</Section>
);
const header = {
padding: '20px 0',
borderBottom: '1px solid #e6e6e6',
};
const tagline = {
fontSize: '14px',
color: '#666',
};Use in templates:
import { EmailHeader } from './components/header';
export const WelcomeEmail = ({ userName }) => (
<Html>
<Body>
<Container>
<EmailHeader />
<Text>Welcome {userName}!</Text>
</Container>
</Body>
</Html>
);Testing Email Templates
Unit Testing with Vitest
import { render } from '@react-email/render';
import { describe, expect, it } from 'vitest';
import { WelcomeEmail } from './welcome';
describe('WelcomeEmail', () => {
it('renders with user name', async () => {
const html = await render(<WelcomeEmail userName="John Doe" />);
expect(html).toContain('Welcome John Doe!');
});
it('includes dashboard link', async () => {
const html = await render(
<WelcomeEmail userName="John" dashboardUrl="https://app.example.com" />
);
expect(html).toContain('https://app.example.com');
});
it('handles missing optional props', async () => {
const html = await render(<WelcomeEmail userName="John" />);
expect(html).toContain('https://app.keplars.com');
});
});Integration Testing
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
async function testEmailSending() {
const html = await render(<WelcomeEmail userName="Test User" />);
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Test Email',
body: html,
}),
});
console.log('Email sent:', response.ok ? 'Success' : 'Failed');
}Best Practices
1. Type Your Props
interface EmailProps {
userName: string;
orderNumber: string;
items: Array<{
name: string;
price: number;
}>;
}
export const Email = ({ userName, orderNumber, items }: EmailProps) => {
// TypeScript ensures correct prop usage
};2. Use Default Values
export const Email = ({
userName,
dashboardUrl = 'https://app.keplars.com',
}: EmailProps) => {
// Falls back to default if not provided
};3. Extract Styles
const styles = {
main: { backgroundColor: '#f6f9fc' },
container: { padding: '20px' },
heading: { fontSize: '24px' },
};
export const Email = () => (
<Html>
<Body style={styles.main}>
<Container style={styles.container}>
<Text style={styles.heading}>Title</Text>
</Container>
</Body>
</Html>
);4. Create Component Library
Organize reusable components:
emails/
├── templates/
│ ├── welcome.tsx
│ └── order.tsx
├── components/
│ ├── header.tsx
│ ├── footer.tsx
│ ├── button.tsx
│ └── layout.tsx
└── styles/
└── theme.ts5. Version Control Templates
// emails/welcome-v2.tsx
export const WelcomeEmailV2 = ({ userName }: Props) => {
// New version with updated design
};
// Gradually migrate users to new version
const template = useNewDesign ? WelcomeEmailV2 : WelcomeEmail;Email Client Compatibility: Test emails across different clients (Gmail, Outlook, Apple Mail). React Email components are designed for broad compatibility, but always test critical emails.
Troubleshooting
Module Resolution Issues
If you see Cannot find module '@react-email/components':
npm install @react-email/componentsOr update your tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "bundler",
"jsx": "react-jsx"
}
}Styles Not Rendering
Inline styles work best for emails:
<Text style={{ color: '#000' }}>Text</Text>Avoid CSS classes or external stylesheets.
TypeScript Errors
Ensure you have React types installed:
npm install --save-dev @types/reactResources
- Official Docs: https://react.email/docs/introduction
- Components: https://react.email/components
- Examples: https://react.email/examples
- GitHub: https://github.com/resend/react-email
Next Steps
Build professional email templates with React Email and send them reliably with Keplars Mail Service.