Build Professional Email Templates in 5 Minutes with React Email, Next.JS, TypeScript, and Tailwind CSS ✉️ 🚀

Hilal
8 min readAug 14, 2024

--

Recently, I discovered how to efficiently create email templates using React Email with Next.js. and the ease of use and flexibility have been impressive. In this article, I’ll walk you through the setup and design of a clean, responsive email template.

Why React Email?

React Email is a powerful tool that allows you to build email templates using React components. It offers flexibility, reusability, and the ability to manage complex email layouts with ease. Additionally, using Tailwind CSS classes enhances the styling process, allowing for rapid, responsive design.

React Email Overview:

React Email is a library designed for creating and sending email templates using React components. It provides a component-based approach to designing email layouts, similar to how you build React applications. Here’s a brief overview of some key components in React Email:

  1. <Html>: The root component that wraps the entire email content. It ensures that the email is formatted correctly as an HTML document.
  2. <Head>: Used to define metadata, styles, and other head elements for the email. It helps in setting up the email's appearance and additional properties.
  3. <Body>: The main container for the email's content. It typically includes the layout and styling elements for your email body.
  4. <Container>: A component that centers and contains the email content, applying consistent padding and borders.
  5. <Section>: Used to group and organize content within the email. It helps in structuring the layout of the email.
  6. <Heading>, <Text>, <Button>, <Img>, <Link>: Components for adding headings, text, buttons, images, and links.
  7. <Tailwind>: A component that applies Tailwind CSS styles to the email. It should be placed appropriately to ensure that styles and media queries are applied correctly.

Getting Started with Next.js and React Email

Before we dive into the template creation process, let’s set up our development environment. I installed the library into my existing project.In your project folder :

npx create-email@latest
cd react-email-starter

This will create a new folder called react-email-starter with a few email templates.

Install and Run locally

npm install
npm run dev

Visit localhost:3000 and edit any of the files on the emails folder to see the changes.

Create Your First Email Template

Under the emails directory, There is an example template which is vercel-invite-user.

vercel-invite-user example template
import {
Body,
Button,
Container,
Column,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Row,
Section,
Text,
Tailwind,
} from "@react-email/components";
import * as React from "react";

interface VercelInviteUserEmailProps {
username?: string;
userImage?: string;
invitedByUsername?: string;
invitedByEmail?: string;
teamName?: string;
teamImage?: string;
inviteLink?: string;
inviteFromIp?: string;
inviteFromLocation?: string;
}

const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";

export const VercelInviteUserEmail = ({
username,
userImage,
invitedByUsername,
invitedByEmail,
teamName,
teamImage,
inviteLink,
inviteFromIp,
inviteFromLocation,
}: VercelInviteUserEmailProps) => {
const previewText = `Join ${invitedByUsername} on Vercel`;

return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={`${baseUrl}/static/vercel-logo.png`}
width="40"
height="37"
alt="Vercel"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Join <strong>{teamName}</strong> on <strong>Vercel</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello {username},
</Text>
<Text className="text-black text-[14px] leading-[24px]">
<strong>{invitedByUsername}</strong> (
<Link
href={`mailto:${invitedByEmail}`}
className="text-blue-600 no-underline"
>
{invitedByEmail}
</Link>
) has invited you to the <strong>{teamName}</strong> team on{" "}
<strong>Vercel</strong>.
</Text>
<Section>
<Row>
<Column align="right">
<Img
className="rounded-full"
src={userImage}
width="64"
height="64"
/>
</Column>
<Column align="center">
<Img
src={`${baseUrl}/static/vercel-arrow.png`}
width="12"
height="9"
alt="invited you to"
/>
</Column>
<Column align="left">
<Img
className="rounded-full"
src={teamImage}
width="64"
height="64"
/>
</Column>
</Row>
</Section>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
href={inviteLink}
>
Join the team
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
or copy and paste this URL into your browser:{" "}
<Link href={inviteLink} className="text-blue-600 no-underline">
{inviteLink}
</Link>
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
This invitation was intended for{" "}
<span className="text-black">{username}</span>. This invite was
sent from <span className="text-black">{inviteFromIp}</span>{" "}
located in{" "}
<span className="text-black">{inviteFromLocation}</span>. If you
were not expecting this invitation, you can ignore this email. If
you are concerned about your account's safety, please reply to
this email to get in touch with us.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};

VercelInviteUserEmail.PreviewProps = {
username: "alanturing",
userImage: `${baseUrl}/static/vercel-user.png`,
invitedByUsername: "Alan",
invitedByEmail: "alan.turing@example.com",
teamName: "Enigma",
teamImage: `${baseUrl}/static/vercel-team.png`,
inviteLink: "https://vercel.com/teams/invite/foo",
inviteFromIp: "204.13.186.218",
inviteFromLocation: "São Paulo, Brazil",
} as VercelInviteUserEmailProps;

export default VercelInviteUserEmail;

Let’s create a sample email template named invitation.tsx under the emails directory, inspired by the vercel-invite-user example which is like this:

import {
Body,
Button,
Container,
Column,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Row,
Section,
Text,
Font,
CodeInline,
Tailwind,
} from "@react-email/components";
import * as React from "react";
import Icon from "@components/Icon";
import classes from "./style";

interface invitationProps {
confirmationLink: string;
}

const invitation = ({ confirmationLink }: invitationProps) => {
const {
discordLink,
twitterLink,
siteUrl,
imageUrl,
containerClass,
hrClass,
imgClass,
buttonClass,
mainLink,
footerLink,
footerClass,
} = classes;
const previewText = `You have been invited to join ${siteUrl}`;
return (
<Html>
<Head>
<Font
fontFamily="Roboto"
fallbackFontFamily="Verdana"
webFont={{
url: "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap",
format: "woff2",
}}
fontWeight={400}
fontStyle="normal"
/>
</Head>
<Preview>{previewText}</Preview>
<Tailwind>
<Body>
<Container className={containerClass}>
<Hr className={hrClass} />{" "}
<Section>
{" "}
<Img
src={`/static/helen.png`}
height="100"
alt="Helen"
width="auto"
className={imgClass}
/>
</Section>
<Section className="mt-[16px]">
<Text className="my-8 text-base font-normal text-neutral-600 ">
You have been invited to create a user on{" "}
<Link href={siteUrl} className={mainLink}>
{siteUrl}
</Link>
. Follow this link to accept the invite:
</Text>
</Section>
<Section className="mb-8 mt-2 text-center">
<Button className={buttonClass} href={confirmationLink}>
Accept Invite
</Button>
</Section>
<Section>
<Text className=" text-sm text-neutral-400">
or copy and paste this URL into your browser:{" "}
<Link href={confirmationLink} className={mainLink}>
{confirmationLink}
</Link>
</Text>
</Section>
<Hr className="my-8w-full mx-0 border border-solid border-neutral-300/30" />
<Section className={footerClass}>
<Link href={discordLink}>
{" "}
<Icon icon="discord" size={18} className=" text-neutral-500" />
</Link>
<Link href={twitterLink}>
{" "}
<Icon icon="x" size={18} className="text-neutral-500" />
</Link>
<Text className="text-xs tracking-tight text-neutral-400 ">
We hope this message was helpful to you. However, if you prefer
not to receive this type of communication from us{" "}
<Link href={siteUrl} className={footerLink}>
click here
</Link>
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};

invitation.PreviewProps = {
confirmationLink: "https://helenstudyo.com/",
} as invitationProps;
export default invitation;
Updated Email Template

What Updates I Made to the Email Template

  1. Tailwind CSS Integration: I’ve utilized Tailwind CSS classes from a separate style file to manage email template styling. This approach allows me to centrally control styles such as text color, link color, and link addresses, making it easier to update multiple email templates consistently.
  2. Font Addition: I’ve incorporated custom fonts by adding a <Font> component from @react-email/components and linking to Google Fonts. This ensures consistent typography across my email templates.
  3. Template Customization: I’ve created a new email template file invitation.tsx, based on the existing Vercel invite user example, while customizing it to fit my needs. The template includes features like a call-to-action button, responsive design, and additional styling options to match my project's branding.

Key Considerations

1.Image Hosting:

Static Files: To ensure that images are properly displayed in your email templates, they must be hosted correctly. Instead of placing images in the static folder and using paths like static/logo.png, you should move them to the public directory of your Next.js project. You can access images using URLs like https://yourwebsitename/logo.png.

Branch and Format:

  • Branch: Make sure the images are committed to the main or master branch of your repository for them to be visible in the final email.
  • Format: Avoid using SVG images, as they are not widely supported by most email clients. Instead, use .png, .gif, or .jpg formats to ensure compatibility across different email platforms.

2.Component Order:

If you are using Tailwind classes that renders into media queries like dark mode, you’ll have to change the order in which the components are defined.

You need to move the Tailwind component from wrapping the <Html /> component followed by the <Head /> component to just directly wrapping the <Head /> component.

const Email = () => (
<Html>
<Tailwind>
<Head />
</Tailwind>
</Html>
);

3.Device Variability:

Be prepared for different results across devices.Even if you set up dark and light themes with media queries, these rules may not always be applied consistently across different devices and email clients. All email clients display dark mode differently.For example gmail mobile app makes full-color inversion or outlook mobile app makes partial color inversion.To address this, minimize background colors and test your emails on various devices and email clients. You can find various techniques to handle these issues effectively online.

color inversion

4.Avoid Importing Tailwind Directly:

Avoid importing Tailwind CSS directly from react-email/tailwind to prevent warnings about unique key props in lists.

Conclusion

For email delivery, integration with different email service providers is essential. I’ll cover these aspects in more detail in a future article.

--

--

Hilal

Hello nice people! I am an enthusiastic frontend developer Here is my notebook to learn things and help others. Don't hesitate to ask me or fix me!