Skip to main content
Technology & EngineeringEmail Template359 lines

React Email

Building email templates with React Email components and rendering pipeline

Quick Summary32 lines
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 lines
Paste into your CLAUDE.md or agent config

React 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 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.

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: 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.
  • 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 a fallbackFontFamily so 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

Get CLI access →