Create Email Templates with Pure React
Learn how to create email templates with react and send them with SMTP.js
Hi everyone, in this article, we are going to create email templates with React components, this can be done in any react framework, the only requirement to do this is to have react-dom
installed. The technologies used in this article are:
Also you can see the finished project here: GitHub Repo
Create the project.
As I already said you can use any react framework or bundler (vite, webpack, etc.) to do this, in my case I'm going to create a Next.js project.
npx create-next-app --ts mail-template
Adding dependencies.
These dependencies are entirely optional, you can use your preferred packages.
- Install Chakra UI:
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
Change your _app.tsx
page to this:
import { ChakraProvider } from "@chakra-ui/react"
export default function MyApp({ Component, pageProps }) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
)
}
- Install React Hook Form:
npm i react-hook-form
Creating the template.
The template is nothing else but a React component, that's it, there is no magic, so let's create a directory called email
at the root of our project, inside there create a file called EmailTemplate.tsx
.
Define the props
On the root of our project create a directory called types
and create a file called Email.ts
inside of it:
In my case, I'm only going to support the following props, but you can define what you want based on your needs:
Add the markup
This step is complete ought to you, create a different markup if you needed but for this example, I'm going to use this:
import { EmailData } from '@/types/Email'
export function EmailTemplate({ subject, name, email, message }: EmailData) {
return (
<body>
<section
style={{
backgroundColor: '#1a202c',
color: '#ffffff',
padding: '2rem',
}}
>
<h1 style={{ textAlign: 'center', textTransform: 'capitalize', marginTop: 0 }}>
{subject}
</h1>
<h3>
Name: <span style={{ color: '#81e6d9' }}>{name}</span>
</h3>
<h3>
Email:{' '}
<a href={`mailto:${email}`} style={{ color: '#81e6d9' }}>
{email}
</a>
</h3>
<article
style={{
backgroundColor: '#308c7a4c',
color: '#81e6d9',
borderRadius: '0.5rem',
padding: '1rem',
}}
>
<h3 style={{ margin: 0 }}>Message:</h3>
<p style={{ margin: 0, fontSize: '1rem' }}>{message}</p>
</article>
</section>
</body>
)
}
As you can see I'm using inline styles, Why? This is because you need your styles already embedded on your markup, if you try to use a CSS file instead your styles won't be applied.
Configuring SMTP.js
Before we continue with the form component, we need to configure SMTP.js.
SMTP.js is a completely free solution to send emails from the frontend, but there is a caveat to consider, the maintainers have absolutely 0 spam tolerance, so you need to apply some measures before launching an app to production.
You need an SMTP server, if you don't have one, you can use your Gmail account, but you need to enable your account for "less secure apps".
Now you need to go to the SMTP.js website and click on:
After that, you need to write your SMTP credentials, (if you are using Gmail, you can use the same exact values below).
This will generate a token that we need to save on a .env.local
file at the root of our project, create a variable, and paste the token there, also create another variable to store your email.
NEXT_PUBLIC_EMAIL_TOKEN="secret token"
NEXT_PUBLIC_EMAIL="my@email.com"
Install SMTP.js SDK
Now click download in SMTP.js website:
And save the file inside the email directory that we created with a .ts
extension:
Open the file and add on line 2 the comment // @ts-ignore
, and replace var
with export let
:
Build the form.
As you may be already inferred, this is a simple form, 4 text fields, nothing special, so the following is the implementation of this simple form but with react-hook-form
& Chakra UI
.
So create the file under the directory components/forms/SendMail.tsx
:
And add the code:
import { useCallback } from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { Box, Center, Heading, VStack } from '@chakra-ui/layout'
import { FormControl, FormErrorMessage } from '@chakra-ui/form-control'
import { Input } from '@chakra-ui/input'
import { Textarea } from '@chakra-ui/textarea'
import { Button } from '@chakra-ui/button'
import { useToast } from '@chakra-ui/toast'
import { EmailIcon } from '@chakra-ui/icons'
import { SubmitHandler, useForm } from 'react-hook-form'
import type { EmailData } from '@/types/Email'
import { EmailTemplate } from '@/email/EmailTemplate'
import { Email } from '@/email/smtp'
export function SendMailForm() {
const toast = useToast()
const {
handleSubmit,
register,
reset,
formState: { errors, isSubmitting },
} = useForm<EmailData>({ mode: 'onBlur' })
const sendMail: SubmitHandler<EmailData> = useCallback(
async data => {
try {
await Email.send({
SecureToken: process.env.NEXT_PUBLIC_EMAIL_TOKEN,
To: data.email,
From: process.env.NEXT_PUBLIC_EMAIL,
Subject: data.subject,
Body: renderToStaticMarkup(<EmailTemplate {...data} />),
})
toast({
title: 'Mail successfully sent',
description: `The mail was sent to: ${data.email}`,
status: 'success',
position: 'top',
duration: 5000,
isClosable: true,
})
} catch (error) {
console.error(error)
} finally {
reset()
}
},
[reset, toast]
)
return (
<Center h="100vh">
<VStack spacing={7} align="stretch">
<Heading as="h1" alignSelf="center">
Send mail with SMTP.js
</Heading>
<Box maxW="lg" p="5" borderWidth="1px" borderRadius="lg" overflow="hidden">
<form onSubmit={handleSubmit(sendMail)}>
<VStack spacing={7} align="stretch">
<FormControl isInvalid={errors.subject as boolean | undefined}>
<Input
type="text"
id="subject"
placeholder="Subject"
{...register('subject', {
required: 'The subject is required',
minLength: { value: 4, message: 'Minimum length should be 4' },
})}
/>
<FormErrorMessage>{errors.subject?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={errors.name as boolean | undefined}>
<Input
type="text"
id="name"
placeholder="Name"
{...register('name', {
required: 'The name is required',
minLength: { value: 4, message: 'Minimum length should be 4' },
})}
/>
<FormErrorMessage>{errors.name?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={errors.email as boolean | undefined}>
<Input
type="email"
id="email"
placeholder="Email"
{...register('email', {
required: 'The email is required',
pattern: { value: /^\S+@\S+$/i, message: 'Write a valid email' },
})}
/>
<FormErrorMessage>{errors.email?.message}</FormErrorMessage>
</FormControl>
<FormControl isInvalid={errors.message as boolean | undefined}>
<Textarea
id="message"
placeholder="Write your message"
{...register('message', {
required: 'The message is required',
minLength: { value: 10, message: 'Minimum length should be 10' },
})}
/>
<FormErrorMessage>{errors.message?.message}</FormErrorMessage>
</FormControl>
<Button
type="submit"
colorScheme="teal"
size="md"
loadingText="Submitting"
isFullWidth={true}
isLoading={isSubmitting}
leftIcon={<EmailIcon />}
>
Send
</Button>
</VStack>
</form>
</Box>
</VStack>
</Center>
)
}
Explanation.
Basically we are using SMTP.js SDK inside the sendMail
function:
Email.send({
SecureToken: process.env.NEXT_PUBLIC_EMAIL_TOKEN,
To: data.email,
From: process.env.NEXT_PUBLIC_EMAIL,
Subject: data.subject,
Body: renderToStaticMarkup(<EmailTemplate {...data} />),
})
We are passing our secret token, email, and the subject, but the really important thing is in the Body
:
renderToStaticMarkup(<EmailTemplate {...data} />)
renderToStaticMarkup
is being imported from react-dom/server
, this function receives the component which is the Email template that we did and returns a simple string with that markup as the content, that's when the email template is being created. Basically, what we did instead of writing a template string by ourselves, we created a simple React component and transpile it to a string with the tools that React already gives us by default.
To end this tutorial, we only need to import our form to the index page:
import Head from 'next/head'
import { SendMailForm } from '@/components/forms/SendMail'
export default function Home() {
return (
<>
<Head>
<title>Mail Sender</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<SendMailForm />
</>
)
}
Fill the form:
And check your inbox :D