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>
`;
passwordReset.js
Copied to clipboard!

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'
    },
    ...
}
passwordReset.js
Copied to clipboard!

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(';');
passwordReset.js
Copied to clipboard!

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:

Generating query strings in JavaScript

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>
`;
passwordReset.js
Copied to clipboard!

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:

The localization folder structure

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...',
    ...
}
en.js
Copied to clipboard!

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);
index.js
Copied to clipboard!

And the end Result?

HTML email formatted with CSS

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!

Remove ads
Remove ads

📚 Get access to exclusive content

Want to get access to exclusive content? Support webtips with the price of a coffee to get access to tips, checklists, cheatsheets, and much more. ☕

Get access Support us
Remove ads Read more on
Remove ads
Remove ads
🎉 Thank you for subscribing to our newsletter. x This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.