Why Email HTML Is Different
Emails use 1998-era HTML. No CSS Grid. No Flexbox. No external stylesheets. Gmail strips <style> blocks. Outlook uses Word's rendering engine.
React Email lets you write modern JSX, then compiles it to table-based HTML that actually renders across email clients.
Setup
npm install @react-email/components react react-dom
npm install -D @react-email/render
Your First Email Template
// emails/welcome.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from '@react-email/components';
interface WelcomeEmailProps {
username: string;
verificationUrl: string;
}
export function WelcomeEmail({ username, verificationUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to Whoff Agents, {username}!</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://whoffagents.com/logo.png"
width="150"
height="50"
alt="Whoff Agents"
/>
<Heading style={h1}>Welcome, {username}!</Heading>
<Text style={text}>
Thanks for signing up. Verify your email to get started.
</Text>
<Section style={buttonContainer}>
<Button style={button} href={verificationUrl}>
Verify Email
</Button>
</Section>
<Text style={text}>
Or copy this link:
<Link href={verificationUrl}>{verificationUrl}</Link>
</Text>
<Hr style={hr} />
<Text style={footer}>
Whoff Agents · 123 Main St · San Francisco, CA 94105
</Text>
</Container>
</Body>
</Html>
);
}
// Inline styles (required for email)
const main = { backgroundColor: '#f6f9fc', fontFamily: '-apple-system, sans-serif' };
const container = { maxWidth: '560px', margin: '0 auto', backgroundColor: '#ffffff', padding: '40px' };
const h1 = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a' };
const text = { fontSize: '16px', lineHeight: '24px', color: '#444444' };
const button = { backgroundColor: '#0070f3', color: '#ffffff', padding: '12px 24px', borderRadius: '6px', textDecoration: 'none', display: 'inline-block' };
const buttonContainer = { textAlign: 'center' as const, margin: '24px 0' };
const hr = { borderColor: '#e6ebf1', margin: '24px 0' };
const footer = { fontSize: '12px', color: '#8898aa' };
Sending with Resend
npm install resend
import { Resend } from 'resend';
import { render } from '@react-email/render';
import { WelcomeEmail } from '../emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY!);
async function sendWelcomeEmail(to: string, username: string) {
const verificationToken = generateToken();
const verificationUrl = `${process.env.APP_URL}/verify?token=${verificationToken}`;
// Render React component to HTML string
const html = render(
<WelcomeEmail username={username} verificationUrl={verificationUrl} />
);
await resend.emails.send({
from: 'hello@whoffagents.com',
to,
subject: `Welcome to Whoff Agents, ${username}!`,
html,
});
}
Email Templates You Need for SaaS
// 1. Welcome / Email Verification
// 2. Password Reset
// 3. Magic Link Login
// 4. Subscription Confirmation
// 5. Invoice / Receipt
// 6. Trial Ending Soon
// 7. Payment Failed
// 8. Team Invitation
Password Reset Template
export function PasswordResetEmail({ resetUrl, expiresIn = '1 hour' }: PasswordResetProps) {
return (
<Html>
<Head />
<Preview>Reset your password</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Reset your password</Heading>
<Text style={text}>
We received a request to reset your password. Click below to choose a new one.
</Text>
<Section style={buttonContainer}>
<Button style={button} href={resetUrl}>
Reset Password
</Button>
</Section>
<Text style={smallText}>
This link expires in {expiresIn}. If you didn't request this, ignore this email.
</Text>
</Container>
</Body>
</Html>
);
}
Preview in Browser
# React Email dev server
npx email dev
# Opens http://localhost:3000 with live preview of all templates
You see exactly how the email looks before sending—with mobile/desktop toggle.
Testing
import { render } from '@react-email/render';
import { WelcomeEmail } from '../emails/welcome';
test('renders welcome email with username', () => {
const html = render(
<WelcomeEmail username="Alice" verificationUrl="https://example.com/verify?token=abc" />
);
expect(html).toContain('Welcome, Alice!');
expect(html).toContain('https://example.com/verify?token=abc');
});
Transactional email is the first thing users see after signup. Don't use a plain-text fallback.
Complete email templates (welcome, reset, invoice, trial-ending) with Resend integration: Whoff Agents AI SaaS Starter Kit.
Top comments (0)