Osamah Aldoaiss

Write better emails with React and postonents

One of the big pain points we had in the last year at Homelike was emails. We had a non-developer hack them together with foundation and custom CSS to save on resources. The result was a janky experience with templates, that had a lot of styling errors, it also took a long time to create an email, which made it our main blocker for new features sometimes.


Email communication 📞

For a company with tons of customers, staying in contact with them is difficult.

You can’t do it only through Facebook, because not everyone has it. You can’t use Twitter, because not everyone uses it. You can’t use WhatsApp, because not everyone installs it.

So for most companies, email is the most spread out communication tool in their customer pool. Finding someone without an email is extremely rare. It was the tool that was used in the first version of the Internet, when it was a network between universities and scientists. It revolutionised the way we talk with each other in the 90s and early 2000s. Emails are used to verify an account, send booking confirmations, advertise new features and products, inform about changes and of course pesky GDPR emails.

So for a growing company it is important to be able to create emails fast and more efficient. And there are a lot of tools that help you with that, like handlebars and ejs or a tool, I found while researching, named mjml. These are all good ways of creating emails.


In comes React ⚛️

But all our Frontend Devs are amazing React Devs, so we had to either introduce another stack into our company or we can just keep using React.

So during the Christmas holidays, I had a little bit of free time, I sat down and started writing a library that would help us write emails.

So having over 50 email templates in multiple languages, I had the goal of making it as simple to use as possible and making email templates as easy as possible, with a huge flexibility in the aspect of the design of them.

Especially considering that emails can’t use any of the benefits of HTML5 and CSS3, which means table layouts and inline-styles. 😩

I used our existing email templates as a playground. And every time I felt like “Hmm, I am wasting a lot of time on writing something that easy” I thought about abstracting it away into component, without taking away the flexibility of customisation. Which was the reason why I introduced the ProviderWrapper.


Introducing postonents 📧

The result of my work is postonents, a React Email Component library and server-side renderer. You can check it out here on Github.

The premise is to write your emails as a React tree with the components offered in the library or create your own components, that are email client valid. Most important thing is reusability like with any React app. For example we extracted the Header, the Footer and the Support parts out of the Main Template and focussed completely on the content, as those never change across any emails.

This example shows a Verification Code Email with most of the components the library offers.

import React from 'react';
import {
Email,
Container,
Row,
Column,
Header,
FullWidth,
Footer,
Text,
Link,
PostonentsProvider,
} from 'postonents';
// Data here represents the Example Data we might get passed to from the backend.
// This can be anything and you need, you have to define it beforehand
const VerificationEmail = ({ lang, data }) => {
const { verifyToken, email } = data;
return (
<PostonentsProvider theme={{ typo: { fontFamily: 'Roboto, sans-serif' } }}>
<Email
lang={lang}
title={`Verification email for ${email}`}
headStyles="body { background-color: white; padding: 40px 0; }"
headLinks={[
{
type: 'link',
props: {
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css?family=Roboto:300,400,700',
},
},
]}
>
<Header
logo="https://assets.airbnb.com/press/logos/NBC%20Logo.gif"
logoHeight={50}
style={{ marginBottom: 24 }}
/>
<Container alignment="center">
<Row>
<Column>
<Text fontSize={20} fontWeight={300}>
Hello,
</Text>
</Column>
<Column style={{ marginBottom: 24 }}>
<Text fontSize={20} fontWeight={300}>
You just registered with the following email: {email}. To verify
this email please click on the link or the text link below.
</Text>
</Column>
<Column style={{ marginBottom: 24, textAlign: 'center' }}>
<Link
href={`https://example.com/verify/${verifyToken}`}
type="primary"
>
Verify your email
</Link>
</Column>
<Column style={{ marginBottom: 24, textAlign: 'center' }}>
<Link
href={`https://example.com/verify/${verifyToken}`}
>{`https://example.com/verify/${verifyToken}`}</Link>
</Column>
</Row>
</Container>
<FullWidth style={{ marginBottom: 24 }}>
<Container alignment="center">
<Row>
<Column>
<Text>Any other questions? We are happy to help!</Text>
</Column>
<Column small={6}>
<Link
href="https://support.example.com"
fullWidth
type="hollow"
>
Help Center
</Link>
</Column>
<Column small={6}>
<Link href="mailto:info@example.com" fullWidth type="hollow">
Email
</Link>
</Column>
</Row>
</Container>
</FullWidth>
<Footer style={{ color: 'white' }}>
<Container alignment="center">
<Row>
<Column
style={{
textAlign: 'center',
fontSize: 12,
lineHeight: '16px',
fontWeight: 300,
}}
>
Copyright © 2018 NBC, all rights reserved
<br />
registered in the commercial register Narnia
</Column>
</Row>
</Container>
</Footer>
</Email>
</PostonentsProvider>
);
};
export default VerificationEmail;

As you can see it is very declarative and simple to use.


Integrating it into your backend ⚙️

Now the interesting part about this is, that there are multiple valid approaches, that depend heavily on your infrastructure.

For example for my side-project with a heroku powered backend and which is a small side-gig, I added the email templates to the backend itself as a separate folder, which gets compiled to a dist folder with babel before the app starts running. The generated html is then send out.

import { renderEmail } from 'postonents';
import VerificationCodeMail from './templates/VerificationCode';
...
// Get your email templates, pass it as the entry point for the server side rendering
// data in this case should align with the data needed in the template
const html = renderEmail(VerificationCodeMail, { lang: 'en', data });
// This is just a generic send function
sendEmail(html);

Now for our company app we had another approach.

We extracted all templates into their own service, which exports one Entry Point. You need to pass in the template, lang and the email data as emailData and the main Email component will get the correct email template from a switch case statement and pass the data on.

import React from 'react';
import { getEmail } from './utils';
/** utils.js
import { AccountUserDeleted } from './emails';
export const getEmail = name => {
switch (name) {
case 'ACCOUNT_USER_DELETED':
return AccountUserDeleted;
default:
return 2;
}
};
*/
const EmailComponent = ({ template, emailData, lang = 'en' }) => {
const Email = getEmail(template);
return <Email lang={lang} {...emailData} />;
};
export default EmailComponent;
import { renderEmail } from 'postonents';
import EmailContainer from '@company/email-templates';
// EmailContainer is the entry point for the service and it gets the data passed to it
const html = await renderEmail(EmailContainer, {
lang: lang.toLowerCase(),
template,
emailData: data,
});
// generic send function
sendEmail(html);

This way the backend service is not being polluted with frontend code and only needs to concern itself with talking with the email template library, that returns the correct component.


Conclusion

We are currently in the process of moving all our services from handlebars to postonents and we are seeing huge benefits in doing so.

Any and all feedback is welcome and if you have ideas how to improve the library in the future, please hit me up on Twitter or open an issue on Github.

;