← All writing
Craft · · 9 min

Outlook renders with Word

The rounded corner on that button is a vector markup language from 1998, and the reason it still is has nothing to do with anybody being lazy.

HTML Cross-Browser Compatibility

A client asked whether the newsletter could look like the site.

Yes, mostly. Then I spent the rest of the call on the mostly.

It’s a completely fair question and I get some version of it every time a redesign lands. They had just watched us rebuild the front of their business, and the thing that actually goes out to their customers every couple weeks still looked like a stock template with the logo swapped in. Same green, same typeface, the same row of three cards with a rounded button at the bottom of each one. Nothing exotic. All of it stuff the site does without being asked.

The honest answer takes a while, because HTML email isn’t the web with a few things taken away. It’s a separate medium that happens to use similar syntax, its rendering situation is roughly where the web was in 1997. There’s no mechanism by which that ever changes. The call always ends the same way, which is a pause and then the client figuring out that I’m not making excuses.

A word processor, on purpose

Start with the one fact that explains most of the rest. Since 2007, Outlook on Windows has laid out HTML email using the Microsoft Word layout engine. Not a browser engine that Microsoft happened to own. The word processor.

Which was deliberate. I think that’s worth sitting with before anybody gets indignant about it, because they wanted a message composed in Word and a message composed in Outlook to come out the same, and the way you guarantee that is to use one engine for both. Perfectly coherent as a product decision. It also froze the renderer that an enormous share of business email passes through into something that was never meant to lay out a page.

In that engine float does nothing. max-width does nothing. A background image on a div does nothing, padding is ignored on some elements, and margins are a coin flip. Anything you’d reach for to build a layout is simply absent, because a word processor never needed any of it.

It does have VML, which Microsoft submitted to the standards body in 1998, which lost to SVG, and which lives on inside this one program. So when you want the client’s rounded button, or a section with a background image, or the whole nine yards, you write this in 2023, next to your ordinary markup, wrapped in a conditional comment so nothing else has to look at it.

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

Microsoft took conditional comments out of Internet Explorer 10 back in 2012. I deleted the last one I owned out of a client template in June and typed a fresh one into an email in November 🫠

Nobody to send the memo to

Now the part I actually find interesting, which is why none of this gets fixed.

Email is a federated protocol with no version negotiation and no central authority. Anybody can write a client, every client is its own rendering environment, there are a few dozen in meaningful use and hundreds in total, running on operating systems going back decades, and nothing can ever be deprecated because there’s nobody whose job that would be.

Compare the web, which we complain about constantly and which is, next to this, a tightly run organization. Three engines. The people who make them meet, agree on specifications, release features roughly in step, and can remove things when they want to. When a vendor decides something is harmful it goes, and a few years later it’s gone everywhere. On the web you get to write a support matrix, argue about it with a client, and drop rows off the bottom of it as the years pass. Email’s matrix is written for you and nothing ever comes off the bottom.

There’s no way 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 hold still.

I keep turning that over, because decentralized is a thing a lot of us say we want, and email is what it looks like 40 years in. It isn’t a failure of will and nobody defected. There’s just no seat anybody could sit in to make the call, and the protocol precludes inventing one.

Tables all the way down

So, what you actually write. Tables. Nested tables, for layout, with role="presentation" on each one so a screenreader treats them as the scaffolding they are instead of announcing a data table.

Inline styles on nearly everything, because plenty of clients throw away the document head. Gmail has supported embedded styles for a few years now, and the Gmail app signed into a non-Gmail account does not, and the gap between those two sentences is where a good afternoon goes. There’s a Can I Email now, built the same way as the browser one, and it’s the most useful thing to happen to this corner of the job in years.

Which means the real 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 it matches. A tool that exists to undo 25 years of separating content from presentation, run on every send. I don’t hate it. I do think about it.

Nobody writes this by hand anymore 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. They’re the difference between this being a day and this being a week.

newsletter.mjml
<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, which is the single thing I’d keep if I could only keep one. Nearly every rendering disaster I’ve had has been a multi-column layout getting creative in one client, and one column works everywhere, reads better on a phone, and is where the design lands after two rounds of testing anyway. That row of three cards became three cards stacked, which is what it already did on a phone, which is where most of them were going to read it.

The parts that get read first

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

Alt text. A lot of clients block images by default, so a decent share of your recipients read the alt text before they read anything else, and an email that’s one big image is a blank rectangle to them. Same argument I was making about alt text on websites years ago, with worse odds.

Dark mode. Several clients invert your colors for you, with varying enthusiasm and no reliable way to opt out, so the white logo on the white background disappears, or carefully chosen dark text gets flipped onto a dark background. On a site, dark mode is an audit you get to run yourself. Here it happens to you, and the defense is transparent images and colors that survive being turned inside out.

And testing, which you can’t do yourself, because you don’t own a Windows machine with a few versions of Outlook on it plus a phone with the Gmail app signed into a non-Gmail account. There are services that render your email in all of them and hand you screenshots. They’re the only reason anybody knows what their email looks like. It’s the one line in our tooling nobody has ever asked me to justify.

In defense of 1997

It’d be unfair to leave it there, because a good chunk of what looks like backwardness is correct.

There’s no JavaScript in email and there never should be. An email is a document sent to you, unasked, by a stranger. It lands in the same place your password resets do. Running arbitrary code out of that is a hard no, and every restriction that follows from “this is untrusted content from an unknown sender” follows honestly. Google has tried a version with interactive parts in it. I have not once been asked for one.

So I’d rather have the tables. I’d rather explain the tables to a client than explain why their newsletter did something ambitious with their reader’s machine.

If you’re about to build one

I’ve built a few of these over the years and I re-learn the whole thing every time, because none of it stays. Which is on me, possibly. It isn’t reinforced by daily use. It’s specific enough that you can’t reason your way back to it from anything you already know. Every line of VML I’ve ever typed, I typed after finding it again.

Which might be why there’s so little written about it? I went looking while I was in there, across a lot of blogs by people who do this for a living, and turned up basically two posts. For a thing every agency does and every client asks for. I suspect it’s that nobody does it often enough in a row to build an opinion worth publishing. So it stays folklore and gets rediscovered a few times a year by whoever drew the short straw.

The short version, if you’re heading in: one column, use a framework and let it write the tables, write the preview text, and buy the testing service. That covers most of it, and the intersection will be exactly where you left it, which is the one restful thing about this medium. Everything else I know goes stale in two years and this hasn’t moved since I started. Good luck, and go and look at it on a phone before anybody else does.

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.

9 min

Put the box under the button

The popover attribute gave me the top layer last spring and left me doing all the math, and the CSS that finishes the job has landed in one browser and deletes a positioning library, 2 scroll listeners and a resize observer.