Call me old school, but I’m a big fan of email. That’s part of why I built Cortado, an app that allows you to create a custom daily email digest from sources like Twitter, Reddit, YouTube, RSS feeds, and Hacker News.

But while I love using email, I hate developing for it. There are endless combinations of providers and clients, standards suck, and at the end of the day, you can't even be sure your email will get delivered.

After a year of working on Cortado, however, I think I’ve finally learned how to make well-designed emails and get them reliably delivered to inboxes.

These are the lessons I learned along the way. My hope is that by sharing them all in one place, with links to tools and additional articles, I'll save others some of the pain that I went through.

Know what CSS and HTML you can use

Want to use flexbox? Nope. Change the opacity of an element? Forget it. Add a box-shadow? Not going to happen.

A user on Hacker News recently wrote this about email: “Imagine you're building a site with one foot in 1999 and another foot in 2016, but instead of 4-5 browsers you have around 20 highly-idiosyncratic browsers past and present to deal with.”

A consequence of this chaos is that many features from CSS and HTML are off limits, even ones that are widely supported for general web development. So try to include only the simplest CSS and HTML, and use resources like this guide from Campaign Monitor or caniemail.com to determine how widespread support is for the features that you do want to use.

Inline all style directives, and get a good library to help

Speaking of CSS, there’s really only one way you should use it in email, and that’s to inline all of it. That means no external CSS files, no <style> tags, and no selectors. Snippets of code like this are distressingly common:

<td style="font-weight:400; padding:0; font-family:Helvetica, Arial, sans-serif">
  <a target="_" href="https://github.com/GRVYDEV/Project-Lightspeed" style="text-decoration:underline; color:#151C1C">Show HN: Lightspeed - subsecond, open source, self hosted stream from OBS</a>
</td>```

If you're thinking that this looks like a pain to write, you’d be correct. Luckily there are libraries that can help. I’ve had a great experience with premailer, a Python library that processes selector-based CSS and inlines it into your HTML. And if you use Node.js instead of Python, you can try juice or inline-css.

Don’t rely on CSS inheritance

If you look carefully at the code snippet above, you’ll notice that the font is being set for an individual table cell. Why not set it for the overall table, or better, the <body> tag? This seems like it would be cleaner since you wouldn't have to repeat the font declaration for all of the cells.

The problem is that the email client can inject CSS that can interfere with inherited styles.

I ran into this issue when I was trying to ensure that Helvetica was used throughout my email. This is not my favorite font, but as we'll see below, options for email fonts are very limited, and I like it better than the typical defaults of email clients like Arial or Roboto.

All of my content was in a big table (again, see below for why), and I had inlined the directive font-family:Helvetica, Arial, sans-serif at the table level. But all of the text in the email, when viewed in the GMail web client, was in Roboto. Eventually, I realized that GMail loaded CSS that set the font for all <td> elements to Roboto. So I had to ensure that every single <td> tag had an inlined font directive with Helvetica to override GMail's behavior.

Party like it’s 1999 — Use HTML tables to format everything

In the 90s, when CSS was still in its infancy, developers commonly used HTML tables to format their content. Since CSS support is so spotty for email, that’s still the best way to get reliable layouts in email.

Here’s a screenshot of an email from Cortado:

Screenshot of a Cortado email, formatted with multiply-nested HTML tables.

I’m happy with how the alignment of this email turned out, but it required tedious work with multiply-nested tables to make it happen. For example, each of the gray bullets in the Hacker News section is a middle dot that occupies its own table cell.

For some further reading on best practices for using tables in HTML emails, I recommend MailChimp’s Email Design Reference or Campaign Monitor’s guide for coding an HTML email.

Choose one of eight fonts

Google and Adobe Fonts are great for web development, but you can’t use them for email. Most clients will block emails from downloading custom fonts, so you are stuck with choosing web safe fonts, those that are widely available across browsers and operating systems. Think Arial, Verdana, Helvetica, Georgia, Tahoma, Trebuchet, and Times New Roman.

One tip: consider using system-ui font family declarations if you want the email’s font to look consistent with the user’s OS. For example, I noticed that The Skimm, which has a great, clean design that I used for inspiration, has the following font family declaration:

font-family:-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;

Dark mode is your enemy

Who doesn’t like dark mode? Well, after trying to design a reasonable looking email that looks good in both light and dark mode, I don’t.

There is a lot of variation in how emails are modified when viewed in dark mode. Some clients don’t change the email at all. Some partially invert the colors, and some fully invert the colors. This heterogeneity would be fine if you could use something like prefers-color-scheme to modify your CSS depending on whether or not the user is in dark mode. But support for this feature is thin.

I've found that this means two things:

  • All images should have transparent background
  • Any colored text or images should be easily visible against both dark and light backgrounds

The second bullet in particular is quite constraining. In practice, it seems to mean that most of the colors in the email (at least those that will appear against the background) need to be close to 0% or 50% luminosity. The ones close to 0% luminosity will get inverted in dark mode, so they will have high contrast in both light and dark mode. But other colors may not get inverted, so anything that’s not close to 50% luminosity will have very different contrast in light and dark mode.

A lot of what I learned about how dark mode affects email came from this helpful guide from Litmus, which has a bunch more useful content on how to deal with dark mode.

Test your email in multiple clients

Even if you follow all of the best practices for creating well-styled emails, there’s no substitute for testing how they look in several different clients. You can do some of this yourself by looking at test emails across a few desktop and mobile clients (in both light and dark mode). There there are also tools that can be helpful, such as Litmus and Email on Acid. Both are a bit pricey, at least for hobbyists or indie developers, but both have free trials.

Use DKIM to make your emails look legit

You may have noticed that some emails you get are marked with phrases like “sent via gmail.mcsv.net” or “sent via amazonses.com”. This happens when the sender’s domain doesn’t match the email provider’s domain. In addition to making your email look shady to the recipient, this can increase the chance that it will be marked as spam.

Fortunately, there’s a relatively easy way to avoid this. You can use the DomainKeys Identified Mail (DKIM) standard to sign emails and prove that they are from the domain they claim to be from. How to set this up varies by which email API service you use, but you can typically find the instructions by Googling “verify domain <service>” (e.g. “verify domain mailgun” or “verify domain sendgrid”).

Choose your email API service carefully

The first email API service I chose was SendGrid. My research process for choosing it was (1) going to its website because I’d heard of it, (2) ensuring it had the features I needed, and (3) noting the trustworthy shades of blue on its landing page. I didn’t give it much more thought because I assumed these services were basically a commodity. What could be so complicated about sending emails?

More than I thought, it turns out. Things went well with SendGrid for a while, but one day I started to hear from users that they weren’t receiving their emails. It turned out that SendGrid had flagged my account as suspicious, despite no major changes and no spam reports, and it had suspended my account to perform a review. This happened without warning, and it took Sendgrid's support team days to respond to my help desk ticket. Luckily, my account was resumed without explanation after about a day.

I assumed that this was an anomaly and didn’t give it much thought. But a few months later I had another issue, and this one caused a five-day outage. It started when I got reports that the emails from Cortado were getting marked as spam. At first, I had no idea how to debug this issue.

Eventually, I found a site called Sender Score, which shows you the reputation of IP addresses that are used to send email. This is an important signal that providers use when determining whether an email is spam — more important than, for example, the content of the email, which in my case hadn’t changed.

Like many email APIs, SendGrid sends emails from shared IP addresses for low-volume accounts. When I looked at some of the IP addresses that were getting used for my account, I saw a sudden drop in reputation, along with a sudden increase in volume. It appeared that a spammer was now using the same IP addresses I was using. I filed another ticket with SendGrid, and again the support team was slow to respond. Eventually, I complained on Twitter, which did get a response from SendGrid, but I was told they couldn’t do anything about it, and that I should pay to upgrade to a private IP address. This was not an option for me, since it would've been more money than I could have justified for a free service, and private IP addresses don't even work well for low-volume accounts like mine.

The time another sender tanked the reputation of my IP and sent all of my emails to spam.

After five days of this outage, I had no option but to change my email API service. This time, I did more research, and I eventually settled on MailGun because it had a good reputation and was inexpensive. It took a few hours to make the switch. Luckily, after a few months, I haven't had any issues with MailGun.

I share this lesson not to throw shade on SendGrid. I’d imagine they just don’t prioritize smaller customers like me. And I’m sure MailGun isn’t for everyone. But it's important to research the email API service you choose, since it can be pretty annoying if things go wrong.

To research the reputation of an email API service, you can search around on Twitter or check review sites like g2 or capterra.

Tools and further reading

For convenience, here are all of the links from above in one place: