← All writing
Craft · · 7 min

HTML email is stuck in 1997

Nested tables in 2023, a markup language from 1998 you will be writing this year, and no version two coming.

HTML Cross-Browser Compatibility

“Can the newsletter look like the site?”

Reasonable question. Same brand, same typeface, same cards. And the honest answer takes about four minutes to deliver and always lands the same way, which is the client slowly realizing I’m not making excuses.

Because HTML email is not the web with some restrictions. It’s a separate medium that happens to use similar syntax, with a rendering situation roughly comparable to 1997, and no mechanism by which that will ever change.

I want to write this as a technical story rather than a complaint, because the reasons are genuinely interesting and most of them aren’t anybody being lazy.

Outlook renders with Word

Start with the fact that explains the most.

Since 2007, Outlook on Windows has rendered HTML email using the Microsoft Word layout engine. Not a browser engine. The word processor.

That was a deliberate decision: they wanted what you compose in Word and what you compose in Outlook to behave identically, and the way to guarantee that is to use one engine. Perfectly coherent as a product decision, and it froze the renderer used by an enormous share of business email into something that was never intended to lay out a web page.

Practically, in that engine: float doesn’t work. max-width doesn’t work. Background images on a div don’t work. Padding on some elements doesn’t work. Margins are unreliable. Anything approaching a modern layout technique is simply absent, because a word processor has no reason to have it.

What it does support is VML, a vector markup language Microsoft proposed to the standards body in 1998, which lost to SVG, and which lives on inside this one program. So when you want a button with rounded corners that survives in Outlook, or a section with a background image, you write this, in 2023, next to your ordinary markup, inside a conditional comment:

button.html
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"
             href="https://example.com" style="height:44px;v-text-anchor:middle;width:200px"
             arcsize="12%" fillcolor="#2f6f4e" stroke="f">
  <w:anchorlock/>
  <center style="color:#ffffff;font-family:sans-serif;font-size:16px">Read more</center>
</v:roundrect>
<![endif]-->
HTML

Conditional comments, which stopped working in Internet Explorer 11 years ago, still work here. I deleted my last one from a website in June and wrote this one in November.

I deleted my last conditional comment from a website in June and wrote a new one for an email in November.

Why there’s no email 2

Now the interesting bit, and the reason this can’t be fixed.

Email is a federated protocol with no version negotiation and no central authority. Anyone can write a client. Every client is its own rendering environment. There are dozens in meaningful use and hundreds in total, running on operating systems going back decades, and nobody can deprecate anything because there is nobody whose job that would be.

Compare the web, which we all complain about constantly. There are three engines. The people who make them meet, agree on specifications, ship features roughly in step, and can and do remove things. When a browser vendor decides a feature is harmful, it goes, and within a few years it’s gone everywhere. That coordination is the reason I could write a post last year about a browser being retired.

Email has no equivalent of any of that. There’s no mechanism to say “from 2024, clients should support flexbox,” because there’s nobody to say it to and no consequence for ignoring it. So the effective standard is the intersection of what every client already does, and an intersection can only shrink or stay still.

That’s not a failure of will. It’s what a decentralized system looks like after 40 years, and it’s worth sitting with, because “decentralized” is a thing a lot of us say we want.

What you actually write

Tables. Nested tables, for layout, with role="presentation" on each so that assistive technology treats them as layout rather than data.

Inline styles on nearly everything, because plenty of clients strip or ignore the document head. Gmail has supported embedded styles for a few years now, and the Gmail app configured with a non-Gmail account does not, and that distinction is where a good afternoon goes.

Which means the actual workflow is that you write a stylesheet like a normal person and then run a build step whose entire purpose is to inline every rule onto every element. A tool that exists to undo 25 years of separating content from presentation, run on every send.

Nobody hand-writes this any more and you shouldn’t either. There are frameworks that take a sane component syntax and compile it down to the tables and the VML and the inlining, and they are the difference between this being a day and being a week.

what it looks like before compilation
<mj-section>
  <mj-column>
    <mj-text font-size="16px">Hello</mj-text>
    <mj-button href="https://example.com">Read more</mj-button>
  </mj-column>
</mj-section>
HTML

And one column. I’d say that’s the single most useful piece of advice in this post. Almost every rendering disaster I’ve had has been a multi-column layout doing something different in one client. One column works everywhere, reads better on a phone, and is where every email design ends up after two rounds of testing anyway.

The parts nobody does

Preview text. The line that appears in the inbox after the subject. It’s the second most-read text you’ll write and it’s set by whatever happens to be first in your HTML, which by default is “View this email in your browser.” Put a hidden element at the top with a real sentence in it.

Alt text. Many clients block images by default, so a large proportion of recipients see your alt text before they see anything else, and an email that’s one big image is, to them, a blank rectangle.

Dark mode. Several clients forcibly invert colors, with varying enthusiasm and no reliable way to opt out. Your white logo on a white background becomes invisible, or your carefully chosen dark text gets inverted onto a dark background. Test it, and prefer transparent images and colors that survive inversion.

Actual testing. You cannot check this yourself, because you don’t have 30 email clients on 11 operating systems. There are services that render your email in all of them and give you screenshots, and they’re the only way anybody knows what their email looks like. It’s the one part of my toolchain with no free equivalent and I’ve never resented paying for it.

Why some of it is right

I’d be unfair if I left it as pure lament.

There’s no JavaScript in email, and there never should be. An email is a document sent to you, unasked, by a stranger, which lands in the place where your password resets live. Executing arbitrary code from that is obviously unacceptable, and every restriction that follows from “this is untrusted content from an unknown sender” is correct.

A lot of what looks like backwardness is actually that, and I’d rather have the tables than a medium where anything can run.

If you’re about to do one

I’ve now written four newsletter templates in my career and I go and re-learn this every time, because the knowledge doesn’t stay: it isn’t reinforced by daily use and it’s specific enough that you can’t reason your way back to it.

Which might be why there’s almost nothing written about it. I went looking, across a lot of blogs by people who do this for a living, and found essentially two posts. For a thing every agency does and every client asks for.

If you’re about to do one: one column, use a framework, write the preview text, and buy the testing service. That’s most of it.

Read similar posts
6 min

Everything is a div

Div soup is what you write before you learn what the other hundred elements are for. The argument for fixing it isn't screenreaders or SEO, it's the next person who opens the file.

6 min

Heading levels are an outline

On a page I audited last month the only `h1` was the logo, the section headings were all `h4` because `h4` was the right size, and the actual page title was a `div` with a class on it.