How to Build Email Templates with React Email + Resend
·APIScout Team
react emailresendemail templatestutorialapi integration
How to Build Email Templates with React Email + Resend
Email HTML is notoriously awful. React Email fixes that by letting you build email templates with React components — version-controlled, type-safe, and testable. Pair it with Resend for delivery and you get a modern email stack.
What You'll Build
- Reusable email component library
- Welcome, receipt, and notification templates
- Live preview during development
- Responsive email layout
- Send via Resend API
Prerequisites: React/Next.js, Resend account (free: 3,000 emails/month).
1. Setup
npm install @react-email/components resend
npm install -D react-email
Add to package.json:
{
"scripts": {
"email:dev": "email dev --dir emails",
"email:export": "email export --dir emails --outDir out"
}
}
2. Base Layout
// emails/components/Layout.tsx
import {
Body, Container, Head, Html, Preview,
Section, Text, Link, Hr,
} from '@react-email/components';
interface LayoutProps {
preview: string;
children: React.ReactNode;
}
export function Layout({ preview, children }: LayoutProps) {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={body}>
<Container style={container}>
{children}
<Hr style={hr} />
<Text style={footer}>
Your App Inc. · 123 Main St · San Francisco, CA 94105
{'\n'}
<Link href="https://yourapp.com/unsubscribe" style={link}>
Unsubscribe
</Link>
</Text>
</Container>
</Body>
</Html>
);
}
const body = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};
const container = {
margin: '0 auto',
padding: '40px 20px',
maxWidth: '560px',
backgroundColor: '#ffffff',
borderRadius: '8px',
};
const hr = { borderColor: '#e6ebf1', margin: '32px 0' };
const footer = { color: '#8898aa', fontSize: '12px', lineHeight: '20px', textAlign: 'center' as const };
const link = { color: '#556cd6' };
3. Email Templates
Welcome Email
// emails/WelcomeEmail.tsx
import { Heading, Text, Button, Section, Img } from '@react-email/components';
import { Layout } from './components/Layout';
interface WelcomeEmailProps {
name: string;
dashboardUrl: string;
}
export default function WelcomeEmail({
name = 'there',
dashboardUrl = 'https://yourapp.com/dashboard',
}: WelcomeEmailProps) {
return (
<Layout preview={`Welcome to Your App, ${name}!`}>
<Img
src="https://yourapp.com/logo.png"
width={120}
height={36}
alt="Your App"
style={{ margin: '0 auto 24px' }}
/>
<Heading style={heading}>Welcome, {name}! 🎉</Heading>
<Text style={text}>
Your account is ready. Here are 3 things to do first:
</Text>
<Text style={text}>
1. <strong>Complete your profile</strong> — Add your company and role{'\n'}
2. <strong>Connect your first API</strong> — Import your API keys{'\n'}
3. <strong>Invite your team</strong> — Collaborate with colleagues
</Text>
<Section style={buttonSection}>
<Button style={button} href={dashboardUrl}>
Go to Dashboard →
</Button>
</Section>
<Text style={subtle}>
Need help? Reply to this email or visit our{' '}
<a href="https://yourapp.com/help" style={{ color: '#556cd6' }}>help center</a>.
</Text>
</Layout>
);
}
const heading = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a', margin: '0 0 16px' };
const text = { fontSize: '16px', color: '#4a4a4a', lineHeight: '26px', margin: '0 0 16px' };
const subtle = { fontSize: '14px', color: '#8a8a8a', lineHeight: '22px' };
const buttonSection = { textAlign: 'center' as const, margin: '32px 0' };
const button = {
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '12px 32px',
borderRadius: '6px',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
};
Receipt Email
// emails/ReceiptEmail.tsx
import { Heading, Text, Section, Row, Column, Hr } from '@react-email/components';
import { Layout } from './components/Layout';
interface LineItem {
name: string;
quantity: number;
price: number;
}
interface ReceiptEmailProps {
customerName: string;
orderId: string;
items: LineItem[];
total: number;
date: string;
}
export default function ReceiptEmail({
customerName = 'Customer',
orderId = '12345',
items = [{ name: 'Pro Plan (Monthly)', quantity: 1, price: 29.00 }],
total = 29.00,
date = '2026-03-08',
}: ReceiptEmailProps) {
return (
<Layout preview={`Receipt for order #${orderId}`}>
<Heading style={heading}>Receipt</Heading>
<Text style={text}>Hi {customerName}, thanks for your purchase!</Text>
<Section style={orderInfo}>
<Row>
<Column><Text style={label}>Order</Text><Text style={value}>#{orderId}</Text></Column>
<Column><Text style={label}>Date</Text><Text style={value}>{date}</Text></Column>
</Row>
</Section>
<Hr style={hr} />
{items.map((item, i) => (
<Row key={i} style={lineItem}>
<Column style={{ width: '70%' }}>
<Text style={itemName}>{item.name}</Text>
<Text style={itemQty}>Qty: {item.quantity}</Text>
</Column>
<Column style={{ width: '30%', textAlign: 'right' }}>
<Text style={itemPrice}>${item.price.toFixed(2)}</Text>
</Column>
</Row>
))}
<Hr style={hr} />
<Row>
<Column style={{ width: '70%' }}>
<Text style={totalLabel}>Total</Text>
</Column>
<Column style={{ width: '30%', textAlign: 'right' }}>
<Text style={totalValue}>${total.toFixed(2)}</Text>
</Column>
</Row>
</Layout>
);
}
const heading = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a' };
const text = { fontSize: '16px', color: '#4a4a4a' };
const orderInfo = { backgroundColor: '#f6f9fc', padding: '16px', borderRadius: '6px', margin: '16px 0' };
const label = { fontSize: '12px', color: '#8a8a8a', margin: '0' };
const value = { fontSize: '14px', color: '#1a1a1a', fontWeight: 'bold', margin: '4px 0 0' };
const hr = { borderColor: '#e6ebf1', margin: '16px 0' };
const lineItem = { margin: '8px 0' };
const itemName = { fontSize: '14px', color: '#1a1a1a', margin: '0' };
const itemQty = { fontSize: '12px', color: '#8a8a8a', margin: '2px 0 0' };
const itemPrice = { fontSize: '14px', color: '#1a1a1a', margin: '0' };
const totalLabel = { fontSize: '16px', fontWeight: 'bold', color: '#1a1a1a' };
const totalValue = { fontSize: '16px', fontWeight: 'bold', color: '#1a1a1a' };
4. Preview
Run the development server to preview templates:
npm run email:dev
Opens at http://localhost:3000 with live reload, device preview (mobile/desktop), and source view.
5. Send with Resend
// lib/email.ts
import { Resend } from 'resend';
import WelcomeEmail from '@/emails/WelcomeEmail';
import ReceiptEmail from '@/emails/ReceiptEmail';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(to: string, name: string) {
return resend.emails.send({
from: 'Your App <hello@yourdomain.com>',
to,
subject: `Welcome to Your App, ${name}!`,
react: WelcomeEmail({ name, dashboardUrl: 'https://yourapp.com/dashboard' }),
});
}
export async function sendReceipt(to: string, order: any) {
return resend.emails.send({
from: 'Your App <billing@yourdomain.com>',
to,
subject: `Receipt for order #${order.id}`,
react: ReceiptEmail({
customerName: order.customerName,
orderId: order.id,
items: order.items,
total: order.total,
date: new Date().toLocaleDateString(),
}),
});
}
Email Client Compatibility
| Feature | Gmail | Outlook | Apple Mail | Yahoo |
|---|---|---|---|---|
| CSS inline styles | ✅ | ✅ | ✅ | ✅ |
| Flexbox | ❌ | ❌ | ✅ | ❌ |
| Grid | ❌ | ❌ | ✅ | ❌ |
| Media queries | ✅ | ❌ | ✅ | ✅ |
| Web fonts | ✅ | ❌ | ✅ | ✅ |
React Email components handle these compatibility issues — they use tables and inline styles under the hood.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Using CSS classes | Styles stripped by email clients | Use inline styles (React Email handles this) |
| Images without alt text | Broken experience when images blocked | Always include alt text |
| No plain text fallback | Some clients can't render HTML | Provide text prop alongside react |
| Too wide layout | Breaks on mobile | Max-width 600px for container |
| Dark mode not considered | Unreadable on dark backgrounds | Test with color-scheme: light dark |
Building email templates? Compare Resend vs SendGrid vs Postmark on APIScout — which email API pairs best with React Email.