Written by Antonello Zanini✏️
Email is an essential communication tool for both businesses and individuals, but creating well-designed and functional emails can be a challenging and time-consuming task for developers. Devs face several face pain points with regard to email creation, such as limited HTML support, compatibility issues across various email clients, and (worst of all) emails landing in spam folders.
In an attempt to breathe new life into the way we write emails, the Resend team has built a library, React Email, that promises to be a next-generation solution for crafting emails. React Email is made up of quality, unstyled components that enable us to create emails with React and TypeScript. This library offers a utility function to compile JSX code into email-friendly HTML that will render seamlessly across all popular email clients.
In this article, we'll take a close look at React Email’s features, explore its styling options (including Tailwind CSS), and learn how to integrate Nodemailer to send emails directly from a Next.js app. We'll also compare React Email to MJML, a popular open source email solution.
Here's a demo of the promotional email that we'll build for a florist site using React Email library:
Jump ahead:
- Prerequisites
- Why React Email?
- Setting up a Next.js project with React Email
- React Email components
- Styling in React Email
- Designing the React Email template
- Passing props in React Email
- Integrating React Email with Nodemailer
- MJML vs React Email
Prerequisites
To follow along with this tutorial, you should have the following:
- Node v.16 or higher installed on your machine
- Familiarity with TypeScript
- Familiarity with the Next.js framework
- Experience with Next.js app router
Why React Email?
React Email is a powerful tool that enables developers to streamline their processes for creating and sending emails. Here are some of React Email’s key features:
- Familiar syntax: Developers who are already familiar with React will find React Email’s learning curve easy and will be able to start building emails right away. Many concepts from React transfer into React Email, like components and props
- Type safety with TypeScript: React Email ships with TypeScript for adding static typing to code. This is helpful for catching errors earlier in development and for writing more robust code
- Ability to test emails during development: React Email leverages the Resend API to allow test emails to be sent during development. This provides an opportunity to preview an email's appearance across different email clients and make necessary adjustments before sending
- Smooth integration with other email service providers: React Email templates can easily be converted to email-friendly HTML and integrated with other email services like Nodemailer, SendGrid, or Resend
- Open source community: React Email is a free and open source library and welcomes development contributions. The React Email community continually adds improvements, fixes bugs, and adds new features; its creators, @ZenoRocha and @Bukinoshita, are also supportive and active in the space
As with any new software, you may encounter bugs while using React Email. If you get stuck, feel free to reach out for help on the tool’s Discord server. You can also report bugs or submit an issue in the React Email GitHub repository.
Now, let's see how to integrate React Email into an existing Next.js project.
Setting up a Next.js project with React Email
To set up a Next.js project with TypeScript, run the following command in your terminal:
npx create-next-app@latest --typescript <project-name>
This will launch a questionnaire to help you configure your application. I have opted in for the new app router and the @/*
alias setting. I named my project florist-app
, since we’ll be creating an email template for a fictional florist site.
After the installation, cd into your project:
cd florist-app
Next, install React Email using any one of the following commands; I’ll be using npm:
# npm
npx create-email@latest
# yarn
yarn create email
#pnpm
pnpm create email
After a successful installation, a new folder, react-email-starter
, will be created within your project directory. This folder will include two subdirectories: emails
, for storing React email templates, and static
, for storing assets such as images.
Now, move into this folder and install the project dependencies:
cd react-email-starter && npm install
Next, run the development server:
npm run dev
The above command will generate a .react-email
folder specifically for email previews. This folder is also a Next.js app and will be installed with all necessary project dependencies. After installation, the preview application will be hosted on localhost:3000
.
N.B., when deploying your project, you'll want to omit .react-email
; you can do this by adding it to your .gitignore
file React Email provides some sample email templates to illustrate how emails are created. To test emails during development, use the Send button at the top right.
In the emails
folder, create a new file, spring-sales.tsx
, for our demo marketing email. We’ll use this demo email to explore the major components of React Email.
React Email components
There are currently 14 React components in the React Email library, including Html
, Image
, Button
, Heading
, Link
, and more. You can view the full list of components here. In this tutorial, we’ll review a few of the components that you’re likely to use most regularly:
-
Html
: React HTML component to wrap emails; all emails must be wrapped with this component -
Preview
: preview text that will be displayed in the recipient's inbox:
-
Image
: used to render images in your emails -
Link
: an anchor tag to link web pages, email addresses, or anything else a URL can address -
Section
: used to display a section that can later be formatted usingColumn
andRow
Let’s explore the available options for adding custom styles to these components.
Styling in React Email
React Email offers two ways to style components — using a style object or using the Tailwind CSS Tailwind
component. It's best not to rely on external (<link rel="stylesheet">
) or embedded stylesheets (styles within the <style>
tag above the <body>
), because many email services cut out everything above the body tag and disable external style sheets.
With that said, let’s go over React Email’s styling options.
Using a style object
Similar to CSS Modules, you can define styles using a JavaScript object in React. To do this, use the style
prop and pass in an object with properties in camelCase representing the CSS properties and their corresponding values as strings:
import { Button } from '@react-email/components'
export const Email = () => {
return (
<Html>
<Button href="https://google.com" style={cta}>
Click me
</Button>
{/* or */}
<Text style={{ color: '#61dafb' }}>Confirm subscription</Text>
</Html>
);
}
const cta = {
padding: '13px 20px',
borderRadius: '5px',
backgroundColor: '#D13928',
textAlign: 'center' as const,
color: '#fff',
display: 'block',
width: '45%',
margin: '0.5rem auto 0 auto',
}
export default Email
If you use TypeScript, you may notice that some values give you a Type 'string' is not assignable to type 'cssProperty'
error. You can address this error by adding as const
at the end.
Using Tailwind CSS
Another option for styling React Email components is to use Tailwind CSS. Import the Tailwind
component into your email and use tailwind
class as normal:
import { Button } from '@react-email/button';
import { Tailwind } from '@react-email/tailwind';
const Email = () => {
return (
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: '#007291',
},
},
},
}}
>
<Button
href="https://example.com"
className="bg-brand px-3 py-2 font-medium leading-4 text-white"
>
Click me
</Button>
</Tailwind>
);
}
There have been some recent complaints about slow rendering while using the Tailwind
component. As a result, I've decided to use the style object approach for this tutorial. Let’s proceed to style our example florist email.
Designing the React Email template
Here’s the promotional email we’ll build for Petal Palace, a fictional florist site: Open the spring-sales.tsx
file in react-email-starter/emails
and add the markup below to create the above email design:
#spring-sales.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Link,
Img,
Text,
Section,
Hr,
Row,
Column,
} from '@react-email/components'
// configure path to assest folder
const baseUrl = process.env.VERCEL_URL ? process.env.VERCEL_URL : '/static'
export const SpringSalesMail = () => (
<Html>
<Head />
<Preview>Spring flower sales💐 Don't miss out!</Preview>
<Body style={main}>
<Container style={parentContainer}>
<Link href='#' style={headingLink}>
Petal Palace
</Link>
<Section style={heroSection}>
<Img src={`${baseUrl}/banner.png`} style={banner} />
<Link href='#' style={cta}>
Get 33% off sale
</Link>
</Section>
<Container style={container}>
{/* more email markup */}
</Container>
</Container>
</Body>
</Html>
)
/* styling objects... */
export default SpringSalesMail
This is an abridged version showing only the important parts; you can view the full email markup in the spring-sales.tsx file in the GitHub repo.
In the above code, we define a variable, baseUrl
, that stores the path to the assets folder. This path can be /static
or it can use your production environment’s URL — this is saved in the VERCEL_URL
environment variable.
We use this approach because when we deploy our project to Vercel, we can't access the static
folder in the same way that we can during development. To solve this issue, we need to copy the email assets into the public
folder at the root of our Next.js project. This makes the assets accessible in production through a URL like https://your-production-url/asset.jpg
.
Also, be sure to copy all environmental variables in .env.local
to your Vercel settings. Here's how your .env.local
should look:
.env.local
VERCEL_URL=https://your-production-url
GMAIL_USER=yourEmail
GMAIL_PASSWORD=yourAppPassword
Passing props in React Email
In React Email, you can pass props just like you would in a regular React component. However, the purpose of these props is to render dynamic values, rather than to execute JavaScript operations. It's important to note that executing JavaScript in emails is not recommended, as it can lead to security vulnerabilities or even mark your emails as spam.
Next, let’s create a TypeScript interface for the email props and use it within the body:
# spring-sales.tsx
import {
Html,
/* other imports */
} from '@react-email/components'
// configure path to assest folder
const baseUrl = process.env.VERCEL_URL ? process.env.VERCEL_URL : '/static'
interface SpringMailProps {
userName: string
}
export const SpringSalesMail = ({ userName = 'Customer' }: SpringMailProps) => (
<Html>
{/* markup... */}
<Container style={container}>
<Text style={paragraph}>Dear {userName},</Text>
{/* markup... */}
</Container>
{/* markup... */}
</Html>
)
/* styling objects... */
export default SpringSalesMail
In the above code, we define a TypeScript interface, SpringMailProps
, with a userName
field. This interface specifies the structure of the props that our SpringSalesMail
component is expecting to receive. If no user name is provided when the email is sent, the variable userName
is initialized with a default value of "Customer".
At this point, you can go ahead and send a test email to yourself.
Now that our email template is built, we’re ready to integrate Nodemailer into our application. This will enable us to send emails to users.
Integrating React Email with Nodemailer
Next.js offers a feature called Route Handlers (previously called API Routes) that allows developers to create serverless functions that are automatically deployed on Vercel. Route Handlers can be used for all sorts of server-side tasks like querying a database. In our case, we‘ll use it to create a route handler that sends emails with Nodemailer, a popular open source library used to send emails from a server.
First, let’s install the necessary dependencies:
# navigate to root and install dependencies
cd ..
npm i @react-email/render nodemailer
This will install the React Email’s render
utility for compiling our React Email templates and the Nodemailer package. We’ll also need to grab the types for the Nodemailer package:
npm i --save-dev @types/nodemailer
Next, we’ll create an email route handler by creating a new folder called email
in app/api
and adding a route.ts
file to it. This file will contain the logic for handling email requests:
# app/api/email/route.ts
import nodemailer from 'nodemailer'
import { NextApiResponse } from 'next';
import { render } from '@react-email/render'
import { SpringSalesMail } from '../../../react-email-starter/emails/spring-sales'
export async function POST(req: Request, res: NextApiResponse){
const { name, email } = await req.json()
const emailHtml = render(SpringSalesMail({ userName: name }));
// this will log the compiled email template created by React Email
console.log(emailHtml)
}
In this code snippet, we retrieve the name
and email
properties from the request body. After extracting the name
, we pass it as a prop to the SpringSalesMail
component. Then, we use the render utility to compile the marketing email and store the resulting HTML code in the emailHtml
variable. Now, we can use Nodemailer’s createTransport
and sendEmail
functions to send emails from this route.
For this example, we’ll be sending emails from a Gmail account. To follow along, you’ll need to log into your Google profile, enable two-factor authentication, and generate an app password. Be sure to store the password and your email address in env.local
under GMAIL_PASSWORD
and GMAIL_USER
, respectively.
Use the code below to send emails from this route:
# app/api/email/route.ts
import nodemailer from 'nodemailer'
import { render } from '@react-email/render'
import { SpringSalesMail } from '../../../react-email-starter/emails/spring-sales'
export async function POST(req: Request) {
const { name, email } = await req.json()
const emailHtml = render(SpringSalesMail({ userName: name }))
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASSWORD,
},
tls: {
rejectUnauthorized: false,
},
})
const mailOptions = {
from: process.env.GMAIL_USER,
to: email,
subject: "Spring flower sales💐 Don't miss out!",
html: emailHtml,
}
// ensure name and email are included
if (!name || !email) {
return new Response(
JSON.stringify({ message: 'Please submit your name and email' }),
{ status: 400 }
)
}
// send email
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error(error)
return new Response(
JSON.stringify({ message: 'Error: Could not send email' }),
{ status: 400 }
)
}
console.log('Email sent: ' + info.response)
return new Response(
JSON.stringify({ message: 'Email sent successfully' }),
{ status: 200 }
)
})
}
This code snippet creates an email transporter using Nodemailer's createTransport()
function with an email service (in our case, Gmail) and authentication credentials stored in environment variables. The tls
object is used to disable server certificate validation; this approach is just for testing purposes and is not recommended for production.
The email details are specified in mailOptions
, with the following fields:
-
from
: set to the same email address stored in theGMAIL_USER
environment variable -
to
: specifies the email address of the recipient — theemail
from the request -
subject
: the email subject line -
html
: contains the HTML content of the email, which is the React Email template stored inemailHtml
.
The code checks if the name
and email
have been submitted. If so, the sendMail()
function is called with mailOptions
. If an error occurs, an error message is returned with a 400 “bad request”. If the email is sent successfully, a success message is returned with a 200 “request status”.
Now, let’s test this functionality on the frontend!
On the app/page.tsx
homepage, use the code below to test the email route:
# app/page.tsx
'use client'
import * as React from 'react'
export default function Home() {
const [name, setName] = React.useState('')
const [email, setEmail] = React.useState('')
const [isSending, setIsSending] = React.useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsSending(true)
try {
const response = await fetch('http://127.0.0.1:3000/api/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
email,
}),
mode: 'no-cors', // disable cors
})
} catch (error) {
console.log(error)
alert('Something went wrong. Please try again.')
} finally {
setIsSending(false)
}
}
return (
<main className='p-4'>
<form onSubmit={handleSubmit}>
<div className='my-2'>
<label htmlFor='Name'>Name</label>
<input
type='text'
className='text-black'
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div className='my-2'>
<label htmlFor='Name'>Email</label>
<input
type='email'
className='text-black'
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<button
type='submit'
className='p-2 transition-colors bg-blue-500 rounded-sm disabled:bg-blue-300'
disabled={isSending}
>
Send Email
</button>
</form>
</main>
)
}
And that’s the full gist of creating and sending emails with React Email!
Now, let’s consider how React Email compares to MJML, a popular open source library in the email templating niche.
MJML vs. React Email
MJML is an open source email framework that was created by Mailjet in 2015 to simplify the process of creating responsive email templates by providing prebuilt components — similar to React Email. MJML has a proven track record and has been tested across multiple email clients.
Despite MJML’s popularity and longevity, there are several reasons why you might consider using a modern email templating solution, like React Email, instead of MJML. Let’s take a look.
Developer experience
The React Email team advocates for and prioritizes improving developer experience. As a result, React Email is easy to use, integrates well with existing email service providers, and includes a provision for testing emails during development. The library was built with developers in mind which is always a win.
Syntax
MJML’s syntax is difficult to scan through as every component is prefixed with mj-
. Here’s a sample:
<mjml>
<mj-head />
<mj-body>
<mj-section>
<mj-text>Hello World!</mj-text>
</mj-section>
</mj-body>
</mjml>
In contrast, React Email's syntax is much cleaner and, if you're familiar with JSX, the learning curve is easier.
Compatibility with modern tooling
React Email was created with the intention of rethinking and innovating the email creation and send processes. By integrating with modern tools like Next.js, SendGrid, and Tailwind, you can be certain that the library is future-proof.
Open source community
React Email has a supportive community that is constantly adding new features, fixing bugs, and helping developers get started with the library. In contrast, MJML's community has dwindled recently. For example, its previous solution for using MJML with React is now deprecated.
MJML is still a solid choice for creating emails, particularly for creating responsive emails as React Email lacks in this department. Moreover, since React Email is new software, you may encounter more bugs. Your choice of email templating solution should ultimately depend on your project's needs.
Conclusion
React Email is a modern tool that simplifies the email creation process by enabling React developers to leverage the power of React's component-based architecture. With React Email, developers can create beautiful and reusable email templates without dealing with cumbersome table syntax.
Despite its many advantages, React Email still has room for improvement. However, the project has a promising roadmap that includes a VS Code extension for previewing emails. As a React developer, you can contribute to the growth of this project by providing feedback and contributing to the community. Even in its current state, React Email offers a great developer experience, and I am excited about its future potential.
All of the code used in this article is available on GitHub.
Get setup with LogRocket's modern React error tracking in minutes:
1.Visit https://logrocket.com/signup/ to get an app ID.
2.Install LogRocket via NPM or script tag. LogRocket.init()
must be called client-side, not server-side.
NPM:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script Tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
Top comments (0)