Back to Blog
React Email: When Your Templating Engine Just Isn't Cutting It Anymore
8 min readJul 19, 20266 views

React Email: When Your Templating Engine Just Isn't Cutting It Anymore

Sending emails from your application usually means wrestling with old templating engines or verbose HTML strings. React Email changes the game, letting you compose rich, responsive emails with the same component model you use for your web apps. It's a breath of fresh air for a long-stagnant problem.

FrontendWeb DevelopmentSoftware DesignReactTooling
Share

by Sunil Band

Emails are a Mess

Let's be honest: building transactional and marketing emails is usually a terrible experience. You're either stuck with some ancient templating language that barely supports logic, or you're concatenating huge HTML strings in your backend. Then comes the real fun: making it responsive across two dozen different email clients, each with its own quirks and rendering engine. Outlook, I'm looking at you.

It's a problem that frontend developers, especially those of us deep in the React ecosystem, have tolerated for too long. We have elegant component models, robust styling solutions, and rich developer tooling for our web apps, but for emails, we regress to a bygone era. This is where React Email steps in, offering a genuinely modern approach to a long-stagnant problem.

Why React for Emails?

The fundamental problem with traditional email templating is the lack of abstraction. You're writing raw HTML and inline CSS, often copy-pasting complex table structures to achieve basic layouts. It's repetitive, error-prone, and utterly devoid of the composability that has made modern frontend development so productive.

React, at its core, is about components. It's about breaking down complex UIs into small, reusable, self-contained units. This paradigm is perfectly suited for email design. Think about it: an email often consists of a header, a footer, a hero section, content blocks, buttons, and social links. These are all natural candidates for components. React Email allows you to leverage this component-based approach, bringing the same development workflow you use for your web applications to your email templates.

It's not just about components, though. React's ecosystem brings a wealth of benefits: type safety with TypeScript, robust testing frameworks, and a familiar development environment. By using React to build emails, you're not learning a new templating language; you're applying existing skills to a new domain.

Getting Started with React Email

React Email provides a CLI that makes setup and development surprisingly smooth. It includes a development server that renders your email components in the browser, complete with hot reloading. This alone is a massive improvement over the typical build-and-send-test-email-to-yourself loop.

First, let's set up a new project:

bash
npx create-email@latest my-email-project
cd my-email-project

This command scaffolds a new project with a few example emails. You'll find a emails directory containing your React components. To see them in action, just run the dev server:

bash
npm run dev

This opens a local web interface where you can browse and interact with your email templates. It's incredibly fast and responsive, allowing for rapid iteration.

Building a Transactional Email Component

Let's create a simple welcome email. We'll leverage some of the built-in components React Email provides, like Html, Head, Body, Text, and Button. These components abstract away much of the cross-client compatibility headaches.

Create a new file, emails/WelcomeEmail.tsx:

```typescript jsx
import { Html, Head, Body, Container, Text, Link, Button, Section, Img } from '@react-email/components';
import * as React from 'react';
interface WelcomeEmailProps {
userName: string;
loginLink: string;
}
export const WelcomeEmail = ({ userName, loginLink }: WelcomeEmailProps) => (
<Html lang="en">
<Head>
<title>Welcome to Our Service!</title>
{/* Basic meta tags for better email client rendering */}
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="x-apple-disable-message-reformatting" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
</Head>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Img
src="https://example.com/logo.png" // Replace with your actual logo URL
width="150"
height="auto"
alt="Your Company Logo"
style={logo}
/>
<Text style={paragraph}>
Hi {userName},
</Text>
<Text style={paragraph}>
Welcome to our service! We're thrilled to have you on board. Get ready to experience something new and exciting.
</Text>
<Text style={paragraph}>
To get started, please log in to your account:
</Text>
<Section style={buttonContainer}>
<Button href={loginLink} style={button}>
Log in to your account
</Button>
</Section>
<Text style={paragraph}>
If you have any questions, feel free to reply to this email.
</Text>
<Text style={paragraph}>
Cheers,
<br />
The Team
</Text>
<Text style={footerText}>
If you did not sign up for this service, please ignore this email.
</Text>
<Link href="https://example.com/unsubscribe" style={footerLink}>
Unsubscribe
</Link>
</Section>
</Container>
</Body>
</Html>
);
// Inline styles are necessary for email clients. React Email handles the conversion.
const main = {
backgroundColor: '#f6f9fc',
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
marginBottom: '64px',
};
const box = {
padding: '0 48px',
};
const logo = {
margin: '0 auto',
marginBottom: '20px',
display: 'block',
};
const paragraph = {
color: '#525f7f',
fontSize: '16px',
lineHeight: '24px',
textAlign: 'left' as const,
};
const buttonContainer = {
textAlign: 'center' as const,
margin: '20px 0',
};
const button = {
backgroundColor: '#673AB7',
borderRadius: '5px',
color: '#fff',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textAlign: 'center' as const,
padding: '12px 24px',
display: 'inline-block',
whiteSpace: 'nowrap' as const, // Prevent button text wrapping unexpectedly
};
const footerText = {
color: '#8898aa',
fontSize: '12px',
lineHeight: '16px',
marginTop: '40px',
textAlign: 'center' as const,
};
const footerLink = {
color: '#8898aa',
textDecoration: 'underline',
fontSize: '12px',
lineHeight: '16px',
textAlign: 'center' as const,
display: 'block',
};

plaintext
plaintext
plaintext
plaintext
plaintext
plaintext
plaintext

Notice a few things here:

1.  **Standard React Props:** We define `WelcomeEmailProps` just like any other React component, making it easy to pass dynamic data into our template.
2.  **Built-in Components:** Components like `Container`, `Section`, `Text`, `Link`, and `Button` abstract away the tedious work of making these elements render correctly across various email clients. They handle the underlying `<table>` soup and inline CSS needed for compatibility.
3.  **Inline Styles:** Email clients demand inline styles. While we write them as JavaScript objects, React Email's build process correctly converts them into inline `style` attributes in the final HTML, along with necessary CSS resets and optimizations.
4.  **`as const` for `textAlign`:** This is a TypeScript trick to ensure the string literal types (`'center'`, `'left'`) are inferred correctly for CSS properties that expect specific string values, preventing potential type errors.

Now, when you run `npm run dev`, you'll see your `WelcomeEmail` component listed and rendered perfectly in the browser. You can even pass props to it in the dev server for testing different states.

## Sending Emails: The Integration

Once you're happy with your email component, you'll need to convert it to a static HTML string to send via your email service provider (SendGrid, Mailgun, AWS SES, etc.). React Email provides a `render` utility for this.

Let's create a small script to generate the HTML:

```typescript jsx
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/WelcomeEmail';
import * as fs from 'fs/promises';
import * as path from 'path';

const outputDir = path.join(process.cwd(), 'dist/emails');

async function buildAndSaveEmail() {
  // Ensure output directory exists
  await fs.mkdir(outputDir, { recursive: true });

  const welcomeEmailHtml = render(<WelcomeEmail userName="Sunil Band" loginLink="https://sunilband.com/login" />, {
    pretty: true, // Optional: makes the HTML output nicely formatted
  });

  const welcomeEmailText = render(<WelcomeEmail userName="Sunil Band" loginLink="https://sunilband.com/login" />, {
    plainText: true, // Generate a plain text version for email clients that don't render HTML or for accessibility
  });

  await fs.writeFile(path.join(outputDir, 'welcome-email.html'), welcomeEmailHtml);
  await fs.writeFile(path.join(outputDir, 'welcome-email.txt'), welcomeEmailText);

  console.log('Welcome email HTML and plain text saved to dist/emails/');
}

buildAndSaveEmail().catch(console.error);

To run this, you'd typically add it to your build process or trigger it dynamically from your backend. For example, you could have an API endpoint that renders the email with specific data for a user before sending it through your chosen service.

Most email service providers allow you to send both an HTML and a plain text version of an email. The plainText: true option in render is incredibly useful for automatically generating a fallback, which is crucial for accessibility and older email clients.

The Trade-offs

While React Email solves many headaches, it's not without its trade-offs:

  1. Bundle Size (for dynamic rendering): If you're rendering emails on the fly in a serverless function or a backend service, you're bringing a small React runtime and the React Email components with it. For high-volume, low-latency scenarios, this might introduce a tiny bit more overhead than a pure string templating engine. However, the developer experience gains often outweigh this.
  2. CSS-in-JS Philosophy: You are primarily writing inline styles, which can feel restrictive if you're used to sophisticated CSS-in-JS libraries or CSS modules. React Email's components handle a lot, but for truly custom elements, you're back to style={...} objects. This is a constraint imposed by email clients, not React Email itself, but it's something to be aware of.
  3. Image Hosting: All images in your emails must be hosted externally (e.g., on a CDN). This is standard for emails, as clients don't embed images directly from your server. Remember to use absolute URLs for all Img src attributes.

Despite these points, the benefits of component reusability, type safety, and the excellent developer preview server make React Email a clear winner for most applications.

Wrapping up

React Email is a game-changer for anyone tired of the archaic ways of building emails. It brings modern frontend development practices to a domain that desperately needs them. The ability to compose emails with React components, see them update in real-time, and automatically handle cross-client compatibility is invaluable.

My advice? Take an existing transactional email from one of your projects – a welcome email, a password reset, an order confirmation – and try to rebuild it with React Email. You'll quickly appreciate how much faster and more enjoyable the process becomes. Head over to the react-email GitHub repository or their documentation to get the create-email CLI running and start converting your legacy templates today. You might just wonder how you ever managed without it.

More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕