Regardless of whether you are building an e-commerce app, a payment solution, a chatting app, a dating app, or whatever the next shiny thing is, you will find yourself integrating emails, usually at the end of the development.
As a developer, I have never enjoyed setting up email deliveries. Why? Because most of the email clients, be it Apple Mail, Gmail, Outlook or anything else, donāt follow a common standard protocol. This results in slight differences in the way the email is shown on these apps. On top of that, the standard email markup is very different from the markups we have come to learn and love, much different from something like Tailwind, Emotion, MUI and others.
react-email, this package lets you write emails in the same way as a standard react component while abstracting away the complexities involved in working with the email markup.
For setup, you can either use the standalone npm project or add it to the existing Node application, like NextJS. In this article, I am going to focus on the existing Node Application since the standalone mode only lets you create the templates out of the box.
I am assuming that you already know how to setup NextJS or a similar NodeJS application.
For Existing Node applications, install the package
npm i react-email @react-email/components"
Add a script to your package.json to run the preview server,
"dev:email": "email dev"
next, let's create a directory called emails and create a file called HelloEmail.tsx
import { Button, Html } from "@react-email/components";
import * as React from "react";
export default function Email() {
return (
<Html>
<Button
href="https://example.com"
style={{ background: "#000", color: "#fff", padding: "12px 20px" }}
>
Click me
</Button>
</Html>
);
}
To see the preview of our email template, run the following command and visit http://localhost:3000
npm run dev:email
Now we can see the live preview of our email. Its too simple.
Letās make it look more beautiful. We will make our template look like StackOverflow newsletter. Copy the following code into the HelloEmail.tsx
import {
Body,
Column,
Container,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
Row,
} from "@react-email/components";
import * as React from "react";
interface StackOverflowTipsEmailProps {
tips?: { id: number; description: string }[];
}
const baseUrl = "https://react-email-demo-bymyam2i5-resend.vercel.app";
const PropDefaults: StackOverflowTipsEmailProps = {
tips: [
{
id: 1,
description:
'To find a specific phrase, enter it in quotes: "local storage"',
},
{
id: 1,
description:
"To search within specific tag(s), enter them in square brackets: [javascript]",
},
{
id: 1,
description:
'Combine them to get even more precise results - [javascript] "local storage" searches for the phrase ālocal storageā in questions that have the [javascript] tag',
},
],
};
export const StackOverflowTipsEmail = ({
tips = [],
}: StackOverflowTipsEmailProps) => (
<Html>
<Head />
<Preview>Stack overflow tips for searching</Preview>
<Body style={main}>
<Container style={container}>
<Section style={logo}>
<Img width={146} src={`${baseUrl}/static/stack-overflow-logo.png`} />
</Section>
<Section style={header}>
<Row>
<Column style={headerContent}>
<Heading style={headerContentTitle}>
Find what you want, faster
</Heading>
<Text style={headerContentSubtitle}>
Tips and tricks for searching on Stack Overflow
</Text>
</Column>
<Column style={headerImageContainer}>
<Img
style={headerImage}
width={340}
src={`${baseUrl}/static/stack-overflow-header.png`}
/>
</Column>
</Row>
</Section>
<Section style={content}>
<Heading as="h2" style={title}>
Searching for solutions
</Heading>
<Text style={paragraph}>
With more than 18 million questions, it's possible that someone
has already provided a solution to the problem you're facing.{" "}
</Text>
<Hr style={divider} />
<Heading as="h2" style={title}>
Use the search bar at the top of the page to find what you need
</Heading>
<Text style={paragraph}>
Here are a few simple search tips to get you started:
</Text>
<ul>
{tips.map((tip) => (
<li key={tip.id}>
<Text style={paragraph}>{tip.description}</Text>
</li>
))}
</ul>
<Text style={paragraph}>
The more information you can put in the search bar, the more likely
you will be to either find the answer you need or feel confident
that no one else has asked the question before.
</Text>
<Hr style={divider} />
<Heading as="h2" style={title}>
Take a break and read about the worst coder in the world
</Heading>
<Section style={buttonContainer}>
<Link style={button} href="https://stackoverflow.blog/2019/10/22/">
I need a break
</Link>
</Section>
</Section>
</Container>
<Section style={footer}>
<Text style={footerText}>
You're receiving this email because your Stack Overflow activity
triggered this tip or reminder.
</Text>
<Link href="/" style={footerLink}>
Unsubscribe from emails like this{" "}
</Link>
<Link href="/" style={footerLink}>
Edit email settings{" "}
</Link>
<Link href="/" style={footerLink}>
Contact us
</Link>
<Link href="/" style={footerLink}>
Privacy
</Link>
<Hr style={footerDivider} />
<Img width={111} src={`${baseUrl}/static/stack-overflow-logo-sm.png`} />
<Text style={footerAddress}>
<strong>Stack Overflow</strong>, 110 William Street, 28th Floor, New
York, NY 10038
</Text>
<Text style={footerHeart}>{"<3"}</Text>
</Section>
</Body>
</Html>
);
StackOverflowTipsEmail.PreviewProps = {
tips: PropDefaults.tips,
} as StackOverflowTipsEmailProps;
export default StackOverflowTipsEmail;
const main = {
backgroundColor: "#f3f3f5",
fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
};
const headerContent = { padding: "20px 30px 15px" };
const headerContentTitle = {
color: "#fff",
fontSize: "27px",
fontWeight: "bold",
lineHeight: "27px",
};
const headerContentSubtitle = {
color: "#fff",
fontSize: "17px",
};
const headerImageContainer = {
padding: "30px 10px",
};
const headerImage = {
maxWidth: "100%",
};
const title = {
margin: "0 0 15px",
fontWeight: "bold",
fontSize: "21px",
lineHeight: "21px",
color: "#0c0d0e",
};
const paragraph = {
fontSize: "15px",
lineHeight: "21px",
color: "#3c3f44",
};
const divider = {
margin: "30px 0",
};
const container = {
width: "680px",
maxWidth: "100%",
margin: "0 auto",
backgroundColor: "#ffffff",
};
const footer = {
width: "680px",
maxWidth: "100%",
margin: "32px auto 0 auto",
padding: "0 30px",
};
const content = {
padding: "30px 30px 40px 30px",
};
const logo = {
display: "flex",
background: "#f3f3f5",
padding: "20px 30px",
};
const header = {
borderRadius: "5px 5px 0 0",
display: "flex",
flexDireciont: "column",
backgroundColor: "#2b2d6e",
};
const buttonContainer = {
marginTop: "24px",
display: "block",
};
const button = {
backgroundColor: "#0095ff",
border: "1px solid #0077cc",
fontSize: "17px",
lineHeight: "17px",
padding: "13px 17px",
borderRadius: "4px",
maxWidth: "120px",
color: "#fff",
};
const footerDivider = {
...divider,
borderColor: "#d6d8db",
};
const footerText = {
fontSize: "12px",
lineHeight: "15px",
color: "#9199a1",
margin: "0",
};
const footerLink = {
display: "inline-block",
color: "#9199a1",
textDecoration: "underline",
fontSize: "12px",
marginRight: "10px",
marginBottom: "0",
marginTop: "8px",
};
const footerAddress = {
margin: "4px 0",
fontSize: "12px",
lineHeight: "15px",
color: "#9199a1",
};
const footerHeart = {
borderRadius: "1px",
border: "1px solid #d6d9dc",
padding: "4px 6px 3px 6px",
fontSize: "11px",
lineHeight: "11px",
fontFamily: "Consolas,monospace",
color: "#e06c77",
maxWidth: "min-content",
margin: "0 0 32px 0",
};
Resend, is the answer. Resend is an email sending service built by the authors of react-email, they have a free usage tier which supports sending upto 3000 emails a month with daily limit of 100 emails. You will need an API key that you can get after signing up with them.
Install the npm package
npm i resend
await resend.emails.send({
from: "mailer@example.com",
to: "receiver@example.com",
subject: "hello world",
react: <StackOverflowTipsEmail />,
});
We have now learned how to design and send beautiful emails in a very developer friendly way. This setup allow us to build maintainable component style emails. I have created a github repository where you can see everything in action.