My Approach to Readable CSS in HTML Emails
Recently, I started working on a password recovery implementation and I was thinking about what would be — if not even the cleanest — but still a good solution to style my email template without having to go mad while reading inline CSS.
See, the problem with HTML emails is that you can’t source out your CSS style declarations, you only have a limited option: exactly one, which is to use inline styles. I wanted to send out the emails through an Express server so it was evident to use JavaScript and hence this article was born.
Before we get into coding, however, It’s important to mention that we have a bunch of user-friendly online tools that have a GUI with which you can create stunning email templates.
For my problem, having it all done in JavaScript seemed to be the optimal and simplest solution. I only needed a single template and also wanted to have full control over everything.
Creating Templates
Starting off, you of course want to have a template. I’ve created a function which exports the following string:
module.exports = () => `
<div style="${getStyles(styles.wrapper)}">
<div style="${getStyles(styles.card)}">
<h1 style="${getStyles(styles.heading)}">We've received a request to reset your password.</h1>
<p style="${getStyles(styles.description)}">Your password has been reset...</p>
<span style="${getStyles(styles.password)}">Your new password</span>
</div>
<a href="https://diettime.app/dashboard" target="_blank" style="${getStyles(styles.link)}">Go to Dashboard</a>
</div>
`;
Here you can see that I’ve used a template literal, so I can wrap HTML elements and make them readable. I’ve solved the inline styling problem with a function call. It gets the CSS styles and turns them into inline styling. But what does the function do internally and where do the parameters coming from?
Adding CSS
First I’ve created a container object for storing the CSS styles and created additional objects inside it to define the CSS properties for each element. These are the objects that are passed down into the function call:
const styles = {
wrapper: {
'background': 'linear-gradient(135deg, #aa00ff 0%,#6a1b9a 100%)',
'border-radius': '4px',
'padding': '50px 25px',
'color': '#FFF',
'text-align': 'center',
'max-width': '600px',
'margin': '20px auto'
},
...
}
Each property inside the object is the name of a CSS property associated with its CSS value. This is what we pass into the getStyles
function whose sole responsibility is to create the inline CSS styles for it.
So what is the solution? It can actually be done in one single line of code:
const getStyles = (object) => Object.keys(object).map(key => `${key}:${object[key]}`).join(';');
We can loop through the object with Object.keys
and map
on each value where we concatenate the key with the value using colons. After that, we can join them together with a semicolon.
The same piece of code has other use cases such as generating query strings:
Adding Localization Support
While at it, while not source out the text itself? That way, we can add localization support in a relatively easy way. All we have to do is pass an object to our function then reference the keys in the template:
module.exports = i18n => `
<div style="${getStyles(styles.wrapper)}">
<div style="${getStyles(styles.card)}">
<h1 style="${getStyles(styles.heading)}">${i18n.passwordResetTitle}</h1>
<p style="${getStyles(styles.description)}">${i18n.passwordResetDescription}</p>
<span style="${getStyles(styles.password)}">${i18n.newPassword}</span>
</div>
<a href="https://diettime.app/dashboard" target="_blank" style="${getStyles(styles.link)}">${i18n.passwordResetGoToDashboard}</a>
</div>
`;
Here I added an i18n
parameter to the function that holds an object to a couple of keys. For each language, you can create a new file holding the necessary translations. I’ve created a separate folder for them called i18n
:
And each of them exports an object with the necessary keys:
module.exports = {
passwordResetTitle: 'We received a request to reset your password.',
passwordResetDescription: 'Your password has been successfully reset...',
...
}
The way you would use them is to import the template and one of the localization files, depending on your needs. You can import them based on some variable, for example, a request param, and call the function to generate the template:
const passwordResetTemplate = require('templates/passwordReset.js');
const i18n = require('../i18n/' + (request.body.locale || 'en'));
// Generate template
passwordResetTemplate(i18n);
And the end Result?
I’ve used images in my template, but for them to appear, you want to send out the emails through a trusted SMTP server. Otherwise your images not only won’t appear, but more importantly, your emails will land in the spam folder.
It’s also important to mention that you have some limitations when it comes to CSS. Not all styles are supported by email clients. For example: although you see a nice box-shadow
applied here, this is the exception. Most of the email clients don’t support it, including Gmail. If you however really insist, you’re better off using images. And since you have more email clients than browsers, making a consistent design for them can be a challenge.
This is why I would advise to only include the most necessary and basic things, such as colors or layout. Everything else should only be a compliment and your emails should look polished even without them.
So how do you actually send out the mails with Express? That deserves its own article which I’m planning to write in the near future, so make sure to follow up. 🍊
Thank you for taking the time to read this article, happy styling!
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: