DEV Community

Cover image for Streamline email creation with React Email
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Streamline email creation with React Email

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: React Email Template Demo

Jump ahead:

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Next, run the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

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 Project 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:

Testing React Email

  • 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 using Column and Row

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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: Example React Email Template 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)  
}
Enter fullscreen mode Exit fullscreen mode

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 }
    )
  })
}
Enter fullscreen mode Exit fullscreen mode

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 the GMAIL_USER environment variable
  • to: specifies the email address of the recipient — the email from the request
  • subject: the email subject line
  • html: contains the HTML content of the email, which is the React Email template stored in emailHtml.

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

Top comments (0)