React Email
Building email templates with React Email components and rendering pipeline
You are an expert in React Email for building modern, component-based email templates. ## Key Points - Always include a `<Preview>` component to control the inbox preview text that appears next to the subject line. - Use inline styles via the `style` prop. React Email handles inlining automatically, but avoid external stylesheets since most email clients strip `<link>` tags. - Provide a plain-text version alongside HTML for clients that do not render HTML and for improved deliverability scoring. - Keep email width at 600px or below. This is the safe maximum across clients. - Use `Container`, `Section`, `Row`, and `Column` for layout instead of raw `<div>` or `<table>` elements. The components abstract away table-based layout quirks. - Type your props with TypeScript interfaces so that calling code gets compile-time safety on dynamic data. - Use the `email dev` command during development for live preview and hot reload. - Store shared styles as plain objects in a separate file (e.g., `emails/styles.ts`) and import them to keep templates DRY. - **Using CSS Grid or Flexbox**: Email clients have very limited CSS support. React Email components use table-based layout under the hood. Avoid passing `display: flex` or `display: grid` in styles. - **Forgetting the `as const` assertion on style properties**: TypeScript may reject string literal types like `"center"` for `textAlign` unless you cast with `as const`. - **Large images without width/height**: Always specify explicit `width` and `height` on `<Img>` to prevent layout shifts in Outlook and other clients that ignore CSS sizing. - **Over-nesting components**: Deeply nested tables degrade rendering in Outlook. Keep your component tree as flat as possible. ## Quick Example ```bash npm install @react-email/components react-email ``` ``` emails/ welcome.tsx reset-password.tsx order-confirmation.tsx package.json ```
skilldb get email-template-skills/React EmailFull skill: 359 linesReact Email — Email Templates
You are an expert in React Email for building modern, component-based email templates.
Core Philosophy
Overview
React Email is a framework that lets you build email templates using React components. It compiles JSX into HTML that is compatible with major email clients, bridging the gap between modern developer experience and the constraints of email rendering engines.
Core Concepts
Project Setup
npm install @react-email/components react-email
A typical project structure:
emails/
welcome.tsx
reset-password.tsx
order-confirmation.tsx
package.json
Add a dev script to preview emails:
{
"scripts": {
"dev": "email dev",
"export": "email export --outDir out"
}
}
Component Library
React Email provides a set of primitive components that map to email-safe HTML:
import {
Html,
Head,
Body,
Container,
Section,
Row,
Column,
Text,
Link,
Img,
Button,
Hr,
Preview,
Font,
Heading,
} from "@react-email/components";
Basic Template Structure
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Text,
Button,
Hr,
} from "@react-email/components";
interface WelcomeEmailProps {
username: string;
activationUrl: string;
}
export default function WelcomeEmail({
username,
activationUrl,
}: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to our platform, {username}!</Preview>
<Body style={main}>
<Container style={container}>
<Section style={section}>
<Text style={heading}>Welcome, {username}!</Text>
<Text style={paragraph}>
Thanks for signing up. Click below to activate your account.
</Text>
<Button style={button} href={activationUrl}>
Activate Account
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
© 2026 Company Inc. All rights reserved.
</Text>
</Container>
</Body>
</Html>
);
}
const main = {
backgroundColor: "#f6f9fc",
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
};
const container = {
backgroundColor: "#ffffff",
margin: "0 auto",
padding: "20px 0 48px",
marginBottom: "64px",
maxWidth: "600px",
};
const section = {
padding: "0 48px",
};
const heading = {
fontSize: "24px",
fontWeight: "bold" as const,
marginTop: "32px",
};
const paragraph = {
fontSize: "16px",
lineHeight: "26px",
color: "#525f7f",
};
const button = {
backgroundColor: "#5469d4",
borderRadius: "4px",
color: "#ffffff",
fontSize: "16px",
textDecoration: "none",
textAlign: "center" as const,
display: "block",
padding: "12px 24px",
};
const hr = {
borderColor: "#e6ebf1",
margin: "20px 0",
};
const footer = {
color: "#8898aa",
fontSize: "12px",
};
Rendering to HTML
Use the render function to convert a React Email component into an HTML string for sending:
import { render } from "@react-email/render";
import WelcomeEmail from "./emails/welcome";
const html = await render(
<WelcomeEmail username="Alice" activationUrl="https://example.com/activate?token=abc" />
);
// Send via any provider
await sendEmail({
to: "alice@example.com",
subject: "Welcome!",
html,
});
For plain-text fallback:
const text = await render(
<WelcomeEmail username="Alice" activationUrl="https://example.com/activate?token=abc" />,
{ plainText: true }
);
Implementation Patterns
Reusable Layout Components
import { Html, Head, Preview, Body, Container } from "@react-email/components";
interface LayoutProps {
preview: string;
children: React.ReactNode;
}
export function EmailLayout({ preview, children }: LayoutProps) {
return (
<Html>
<Head>
<Font
fontFamily="Inter"
fallbackFontFamily="Helvetica"
webFont={{
url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap",
format: "woff2",
}}
/>
</Head>
<Preview>{preview}</Preview>
<Body style={{ backgroundColor: "#f6f9fc", padding: "40px 0" }}>
<Container style={{ maxWidth: "600px", margin: "0 auto" }}>
{children}
</Container>
</Body>
</Html>
);
}
Dynamic Content with Conditional Rendering
interface OrderEmailProps {
items: Array<{ name: string; price: number; quantity: number }>;
discount?: number;
}
export default function OrderEmail({ items, discount }: OrderEmailProps) {
const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
const total = discount ? subtotal - discount : subtotal;
return (
<EmailLayout preview={`Your order total: $${total.toFixed(2)}`}>
<Section>
{items.map((item, index) => (
<Row key={index} style={{ borderBottom: "1px solid #eee", padding: "8px 0" }}>
<Column style={{ width: "60%" }}>
<Text>{item.name} x{item.quantity}</Text>
</Column>
<Column style={{ width: "40%", textAlign: "right" }}>
<Text>${(item.price * item.quantity).toFixed(2)}</Text>
</Column>
</Row>
))}
{discount && (
<Row>
<Column><Text style={{ color: "#22c55e" }}>Discount</Text></Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ color: "#22c55e" }}>-${discount.toFixed(2)}</Text>
</Column>
</Row>
)}
<Hr />
<Row>
<Column><Text style={{ fontWeight: "bold" }}>Total</Text></Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ fontWeight: "bold" }}>${total.toFixed(2)}</Text>
</Column>
</Row>
</Section>
</EmailLayout>
);
}
Integration with 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 sendWelcome(to: string, username: string) {
const html = await render(<WelcomeEmail username={username} activationUrl="..." />);
await resend.emails.send({
from: "onboarding@example.com",
to,
subject: `Welcome, ${username}!`,
html,
});
}
Integration with Nodemailer
import nodemailer from "nodemailer";
import { render } from "@react-email/render";
import WelcomeEmail from "./emails/welcome";
const transporter = nodemailer.createTransport({
host: "smtp.example.com",
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});
async function sendWelcome(to: string, username: string) {
const html = await render(<WelcomeEmail username={username} activationUrl="..." />);
const text = await render(<WelcomeEmail username={username} activationUrl="..." />, { plainText: true });
await transporter.sendMail({
from: '"Company" <noreply@example.com>',
to,
subject: `Welcome, ${username}!`,
html,
text,
});
}
Best Practices
- Always include a
<Preview>component to control the inbox preview text that appears next to the subject line. - Use inline styles via the
styleprop. React Email handles inlining automatically, but avoid external stylesheets since most email clients strip<link>tags. - Provide a plain-text version alongside HTML for clients that do not render HTML and for improved deliverability scoring.
- Keep email width at 600px or below. This is the safe maximum across clients.
- Use
Container,Section,Row, andColumnfor layout instead of raw<div>or<table>elements. The components abstract away table-based layout quirks. - Type your props with TypeScript interfaces so that calling code gets compile-time safety on dynamic data.
- Use the
email devcommand during development for live preview and hot reload. - Store shared styles as plain objects in a separate file (e.g.,
emails/styles.ts) and import them to keep templates DRY.
Common Pitfalls
- Using CSS Grid or Flexbox: Email clients have very limited CSS support. React Email components use table-based layout under the hood. Avoid passing
display: flexordisplay: gridin styles. - Forgetting the
as constassertion on style properties: TypeScript may reject string literal types like"center"fortextAlignunless you cast withas const. - Large images without width/height: Always specify explicit
widthandheighton<Img>to prevent layout shifts in Outlook and other clients that ignore CSS sizing. - Over-nesting components: Deeply nested tables degrade rendering in Outlook. Keep your component tree as flat as possible.
- Not testing the rendered output: Always check the compiled HTML in real email clients. The React preview server is helpful but not a substitute for client testing.
- Relying on web fonts: Many email clients ignore
@font-face. Always specify afallbackFontFamilyso the design degrades gracefully.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add email-template-skills
Related Skills
Dark Mode Email
Dark mode support patterns for email templates across major email clients
Email Accessibility
Accessible email design patterns for inclusive, standards-compliant email templates
Email Deliverability
Email deliverability essentials including SPF, DKIM, DMARC, and inbox placement
Email Testing
Email testing workflows using Litmus, Email on Acid, Mailtrap, and other QA tools
Mjml
Building responsive email templates with the MJML markup language and toolchain
Responsive Email
Responsive email CSS patterns for consistent rendering across clients and devices