Skip to main content
Blog
Mar 4, 2026·10 min read

React Email: The Complete Guide to Building Emails in JSX

React Email lets you write emails with React components instead of fighting with raw HTML tables. Here's everything you need to know — from setup to production deployment.

Why React Email exists

HTML email development has been stuck in 2003. You write <table> layouts, inline every style, and test across a dozen clients that each interpret CSS differently. React Email replaces this with a component-based workflow: you write JSX, and it compiles to the table-based HTML that email clients expect.

The key insight is that React Email components like Row and Column compile to tables automatically — so you get flexbox-like ergonomics without the compatibility issues that come with actual flexbox.

Setup

Terminal
npm install @react-email/components react-email

# or with bun
bun add @react-email/components react-email

React Email provides a dev server (email dev) for local preview. For production rendering, use the render() function to compile JSX to an HTML string.

Core components

React Email ships a small set of components that each compile to email-safe HTML. Here's what each one does:

Html

Root wrapper. Sets DOCTYPE and xmlns.

Head

Contains <meta> tags, <title>, and embedded styles.

Body

Wraps all visible content. Accepts style prop for background color.

Container

Centers content with a max-width. Compiles to a centered <table>.

Row / Column

Table-based grid. Use instead of flexbox or CSS grid.

Section

Groups content blocks. Renders as a <table> row.

Text

Paragraph element with email-safe defaults.

Img

Image with required width/height for Outlook. Always include alt text.

Link

Anchor tag with target="_blank" default.

Button

Call-to-action button using a padded <a> tag for maximum client support.

Hr

Horizontal rule using a <table> border trick for consistency.

A complete example

Here's a welcome email using all the core components. Note the inline styles — React Email supports style objects, not CSS classes:

welcome-email.tsx
import {
  Html, Head, Body, Container,
  Row, Column, Text, Img, Button, Hr
} from '@react-email/components';

export default function WelcomeEmail({ name }: { name: string }) {
  return (
    <Html>
      <Head />
      <Body style={{ backgroundColor: '#f6f6f6', fontFamily: 'Arial, sans-serif' }}>
        <Container style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
          <Img
            src="https://example.com/logo.png"
            width="120"
            height="36"
            alt="Company Logo"
          />
          <Text style={{ fontSize: '24px', fontWeight: 'bold', marginTop: '24px' }}>
            Welcome, {name}!
          </Text>
          <Text style={{ color: '#666', lineHeight: '1.6' }}>
            We're glad you're here. Here's what you can do next:
          </Text>
          <Button
            href="https://example.com/dashboard"
            style={{
              backgroundColor: '#000',
              color: '#fff',
              padding: '12px 24px',
              borderRadius: '6px',
              textDecoration: 'none',
            }}
          >
            Go to Dashboard
          </Button>
          <Hr style={{ borderColor: '#eee', margin: '24px 0' }} />
          <Text style={{ fontSize: '12px', color: '#999' }}>
            You received this because you signed up at example.com
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Styling rules

Email clients have inconsistent CSS support. Follow these rules to avoid surprises:

  • Always use inline styles — Gmail strips <style> blocks entirely
  • No flexbox or grid Outlook ignores both. Use Row/Column instead
  • Set explicit width/height on images — prevents layout shifts in Outlook
  • Use web-safe font stacks — custom fonts via @font-face are unsupported in many clients
  • Avoid shorthand properties — some clients parse margin: 10px 20px differently

Conditional rendering

Because React Email is just React, you get conditional rendering and loops for free. This is one of its biggest advantages over raw HTML templates:

Conditional content
// Conditional content based on props
export default function NotificationEmail({
  type,
  items,
}: {
  type: 'welcome' | 'invoice';
  items?: { name: string; price: string }[];
}) {
  return (
    <Html>
      <Body>
        <Container>
          {type === 'welcome' && (
            <Text>Welcome to our platform!</Text>
          )}

          {type === 'invoice' && items && (
            <>
              <Text>Here's your invoice:</Text>
              {items.map((item, i) => (
                <Row key={i}>
                  <Column>{item.name}</Column>
                  <Column style={{ textAlign: 'right' }}>{item.price}</Column>
                </Row>
              ))}
            </>
          )}
        </Container>
      </Body>
    </Html>
  );
}

Testing across clients

The React Email dev server only shows how your email looks in a browser. It won't catch the CSS properties that Outlook silently drops, the styles Gmail strips, or the dark mode transforms Apple Mail applies.

Emailens can take your React Email JSX source directly — it compiles, transforms per-client, and flags every compatibility issue with fix snippets in React Email syntax.

Test your React Email across 15 clients

Paste your JSX and get a compatibility report with React Email-specific fix snippets.

Preview your email

Free plan — 30 previews/day