React Email: Build and Send Emails with React (Step-by-Step)

React Email

The react-email library by Resend lets you write email templates as React components, preview them in a local browser, and render them to battle-tested HTML with a single render() call. No more wrangling hand-coded table layouts or context-switching to a MJML file: your templates live next to your app code, accept TypeScript props, and get the same version control and component reuse as the rest of your codebase.

This tutorial covers installation, building a real template, rendering to HTML, and sending it. Code is verified against the current react-email docs (v6+).


What You Need Before Starting

  • Node.js 18 or later
  • A React project (or a plain Node.js backend where you’ll render and send)
  • An SMTP account or a transactional email provider API key for the sending step

Step 1: Install react-email

Since react-email v6, all components and the render utility ship from a single package.

npm install react-email

If you need React as a peer dependency and it is not already installed:

npm install react react-dom

For TypeScript projects, types are included in the package.


Step 2: Start the Preview Server

The preview server lets you see your templates in a browser as you build them. Run it from the directory where your email files live:

npx react-email dev

By default the server starts on port 3000. Pass -p 3001 to use a different port. You will see every .tsx or .jsx file in your emails/ folder listed in the sidebar with live reload on save.


Step 3: Build a Template Component

Create an emails/ directory at your project root. Add a file for your first template.

emails/WelcomeEmail.tsx

import * as React from 'react';
import {
  Html,
  Head,
  Body,
  Container,
  Text,
  Button,
  Preview,
} from 'react-email';

interface WelcomeEmailProps {
  firstName: string;
  confirmUrl: string;
}

export function WelcomeEmail({ firstName, confirmUrl }: WelcomeEmailProps) {
  return (
    <Html lang="en">
      <Head />
      <Preview>Welcome to the app, {firstName}!</Preview>
      <Body style={bodyStyle}>
        <Container style={containerStyle}>
          <Text style={headingStyle}>Hey {firstName}, welcome aboard.</Text>
          <Text style={textStyle}>
            Your account is ready. Confirm your email address to unlock
            everything.
          </Text>
          <Button href={confirmUrl} style={buttonStyle}>
            Confirm your email
          </Button>
          <Text style={footerStyle}>
            If you did not create this account, you can safely ignore this
            email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

const bodyStyle: React.CSSProperties = {
  backgroundColor: '#f6f9fc',
  fontFamily:
    '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};

const containerStyle: React.CSSProperties = {
  backgroundColor: '#ffffff',
  margin: '40px auto',
  padding: '40px',
  maxWidth: '560px',
  borderRadius: '8px',
};

const headingStyle: React.CSSProperties = {
  fontSize: '20px',
  fontWeight: '600',
  color: '#111827',
};

const textStyle: React.CSSProperties = {
  fontSize: '15px',
  lineHeight: '1.6',
  color: '#374151',
};

const buttonStyle: React.CSSProperties = {
  backgroundColor: '#2563EB',
  color: '#ffffff',
  padding: '12px 24px',
  borderRadius: '6px',
  fontSize: '15px',
  fontWeight: '600',
  textDecoration: 'none',
  display: 'inline-block',
};

const footerStyle: React.CSSProperties = {
  fontSize: '12px',
  color: '#9CA3AF',
  marginTop: '32px',
};

A few things to note:

  • <Preview> sets the hidden preheader text that email clients show in inbox previews.
  • Styles are inline React.CSSProperties objects. react-email handles the conversion to inline styles that survive email client CSS stripping.
  • Props (firstName, confirmUrl) are plain TypeScript, so you get autocomplete and compile-time type checking everywhere the template is called.

Step 4: Render the Component to HTML

The render() function from react-email converts your component to a plain HTML string that any email provider can accept.

import { render } from 'react-email';
import { WelcomeEmail } from './emails/WelcomeEmail';

const html = await render(
  <WelcomeEmail
    firstName="Jordan"
    confirmUrl="https://app.example.com/confirm?token=abc123"
  />
);

// html is a string of email-safe HTML ready to pass to your sending library

render() is async. It also accepts an options object: pass { plainText: true } to generate a plain-text version of the same template for the text field of your email payload.

const text = await render(
  <WelcomeEmail firstName="Jordan" confirmUrl="https://..." />,
  { plainText: true }
);

Step 5: Send the Rendered HTML

Once you have the HTML string, pass it to your sending library or provider API. Below is an example using Nodemailer. For a full walk-through of Nodemailer configuration including SMTP transport, connection verification, and provider HTTP API alternatives, see how to send email in Node.js.

import nodemailer from 'nodemailer';
import { render } from 'react-email';
import { WelcomeEmail } from './emails/WelcomeEmail';

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: 587,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

const html = await render(
  <WelcomeEmail
    firstName="Jordan"
    confirmUrl="https://app.example.com/confirm?token=abc123"
  />
);

await transporter.sendMail({
  from: '"My App" <[email protected]>',
  to: '[email protected]',
  subject: 'Confirm your email address',
  html,
  text: await render(
    <WelcomeEmail
      firstName="Jordan"
      confirmUrl="https://app.example.com/confirm?token=abc123"
    />,
    { plainText: true }
  ),
});

If you use a provider API instead of SMTP, the pattern is identical: call render(), then pass the resulting string as the html field in your API request. See the official react-email integration docs for provider-specific examples (Resend, SendGrid, Postmark, Mailgun, and others). To compare providers before committing to one, see the transactional email service comparison.


react-email vs. Hand-Coded HTML vs. MJML

react-emailHand-coded HTML tablesMJML
Authoring languageTSX/JSXRaw HTMLMJML XML
Type-safe propsYesNoNo
Component reuseYes (React imports)Copy-pastePartial (mjml-component)
Live previewBuilt-in dev serverBrowser + email clientMJML online editor
OutputEmail-safe HTMLDepends on skillEmail-safe HTML
ToolchainNode.js / npmAnyNode.js / npm
Version control fitExcellentDecentDecent

Compatibility and Limits

react-email generates HTML that targets the email client compatibility matrix maintained by the project. That said, a few caveats apply:

  • Tailwind support: react-email ships a <Tailwind> component that applies inline styles from Tailwind utility classes. This covers most clients, but CSS-in-email has known gaps (particularly in Outlook on Windows, which uses the Word rendering engine).
  • JavaScript in email: Rendered output is static HTML. Any interactivity (AMP for Email, interactive carousels) requires a different rendering path; react-email is not the right tool for those.
  • Server-side only: render() runs in Node.js. It is not suitable for client-side rendering in a browser without polyfilling the ReadableByteStreamController API.

For a deeper look at what makes email land in the inbox rather than spam, including authentication setup, see the guide on transactional email.


Is react-email free to use?

Yes. react-email is open source (MIT license) and published by Resend at github.com/resend/react-email. There is no cost to install or use the library. Sending emails still requires a separate SMTP account or transactional email provider, each of which has its own pricing.

Do I need to use Resend to send emails with react-email?

No. react-email is a rendering library: it converts React components to HTML strings. You can pass that HTML string to any provider or SMTP server (Nodemailer, SendGrid, Postmark, Mailgun, and others). Resend is one option; it is not required.

Does react-email work with Next.js?

Yes. You can use react-email in a Next.js API route or Server Action. Call render() server-side (in a Route Handler, app/api/..., or a use server action), then pass the resulting HTML to your email provider. The preview dev server runs separately from your Next.js app and is only needed during development.

How do I add dynamic data / merge tags to a template?

Pass data as props to your component, the same way you would in any React component. The render() call accepts the JSX element with those props, so await render(<WelcomeEmail firstName="Jordan" confirmUrl="https://..." />) inlines all dynamic values at render time. There is no separate merge tag syntax to learn.

Can I use Tailwind CSS with react-email?

Yes. Wrap your template in the <Tailwind> component exported from react-email. It processes Tailwind utility classes and converts them to inline styles compatible with most email clients. Full CSS support varies by client, so test rendered output in an email preview tool before deploying.

What is the difference between react-email and @react-email/components?

As of v6, react-email is the unified package that includes all components (previously split across @react-email/components and individual packages) and the render utility. Importing from react-email is the current recommended approach. The older @react-email/components and individual component packages are deprecated.