Notifio watches rental listing search pages from your own machine and emails you the moment a new listing appears. The email is the product: a listing that goes in twenty minutes is only worth knowing about if you can read the message now.

I have written before about how those templates are built, four messages each with an HTML and a plain-text twin, all string concatenation. This is about something I did not look at then: the colours.

The count

Three template files, six HTML documents between them.

File Colour refs Backgrounds Text Borders Distinct
emails/alert.ts 51 12 26 13 7
emails/free-access.ts 22 4 14 4 8
emails/activation.ts 19 4 11 4 8
Total 92 20 51 21 8

Eight greys do all the work, from #0a0a0a at the back to #fafafa at the front:

#0a0a0a #171717 #262626 #404040 #525252 #737373 #a3a3a3 #fafafa 

Every one of those 92 references is inline, because a meaningful share of email clients still strip <style> blocks. And the counts that matter are the ones that are zero:

$ grep -c "<style\|@media\|!important" src/lib/emails/*.ts alert.ts:0 free-access.ts:0 activation.ts:0 

No style block, no media query, no !important, in any of them. Here is the entire <head> of every email we send, repeated six times:

<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> </head> 

Twelve meta tags across six documents, and not one of them says anything about colour.

Why that matters more for a dark email than a light one

Email clients deal with dark mode in three different ways, and the one you have to design for is the third.

  1. Ignore it. The email renders as authored. This is the majority, and it is fine.
  2. Respect what the email declares. Apple Mail and iOS Mail look for a color-scheme declaration. If they find one saying the email handles dark mode itself, they leave it alone. If they do not, they may apply their own adjustments to colours they consider unset.
  3. Rewrite the colours. Outlook.com and the Windows Outlook clients have historically inverted or remapped colours in a dark reading environment, with no way for the sender to opt out.

A light email that gets darkened usually survives, because the transforms are tuned for exactly that case. A dark email that gets lightened is a different story, and that is what we are shipping: 20 background declarations that are all near-black, and 51 text declarations that are all near-white, with no statement anywhere that this was on purpose.

The specific failure is one element. In the listings email, the listing link is the whole point of the message:

<a href="${escHtml(l.url)}" style="color:#fafafa;font-weight:600;text-decoration:none;font-size:14px;"> ${escHtml(l.title)} </a> 

color:#fafafa, which appears eight times in that file. Partial inversion, where a client rewrites the background it recognises and leaves inline text colours alone, puts #fafafa text on a light background. White on white. The room you were being told about is still in the HTML, and the reader cannot see it.

One thing the current design gets right by accident

Worth saying, because the audit is not all bad. Those 20 background declarations are not redundant. The body carries background:#0a0a0a, and then the outer table carries it again, and the card carries #171717 separately:

<body style="margin:0;padding:0;background:#0a0a0a;..."> <table width="100%" style="background:#0a0a0a;padding:48px 24px;"> <tr><td align="center"> <table width="560" style="background:#171717;border:1px solid #262626;border-radius:8px;"> 

Every surface states its own colour rather than inheriting one. A client that drops the <body> background, which several do, does not expose a white page behind a dark card, because the table underneath declares the same colour. The repetition that looks like sloppiness in a web page is a durability feature in an email.

The three lines that were missing

The fix is cheap and it is mostly declaration, not design:

<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="color-scheme" content="dark"> <meta name="supported-color-schemes" content="dark"> <style>:root { color-scheme: dark; supported-color-schemes: dark; }</style> </head> 

The two meta tags are the part that survives a client stripping <style>, which is why both forms go in rather than just the tidier CSS one. Together they tell a client in category 2 that this email has already handled dark mode and needs no help. They do nothing at all for category 3, and there is no honest way to pretend otherwise: against a client that rewrites colours regardless, declarations are not the lever.

For that case the lever is making the important element survive a colour change rather than depend on one. Which brings me to the line I would change on its own merits:

text-decoration:none 

There are three anchors across all six emails, and two of them turn off the underline. In a web page that is a defensible taste call next to the rest of the styling. In an email whose colour may be rewritten by software you cannot test against, the underline is the only cue that survives: it says "this is a link" in a channel where color:#fafafa may no longer say anything. Taking it off meant the link's entire affordance was a colour, and colour is the one property in this medium you do not control.

So the underline goes back on the listing link in the alert email. Not for dark mode specifically, but because a product whose single job is getting you to click a listing fast should not spend its one affordance on a value a mail client is free to overwrite.

What I did not do

I did not build a light variant. A second colour scheme means every one of those 92 declarations becomes a pair, enforced by nothing, in a medium where I cannot write an assertion that fails. The honest options were "declare that it is dark and make the critical element robust" or "rewrite all six emails in a scheme that reads either way", and for a one-person product the first is the one that stays true after the next edit.

I also did not add bgcolor attributes next to the inline backgrounds. There are zero in the templates today, and the presentational attribute does still get honoured by some older clients where inline CSS does not. That one is a real gap. It is on the list, below the underline.

Takeaways

  • Count the zeroes. The useful finding here was not the 92 colours, it was the 0 style blocks and the 12 meta tags that never mention colour.
  • A dark-themed email is a stronger claim than a light one, because the client transforms are tuned for darkening. Declare it with both the meta tags and the CSS, since either can be stripped.
  • Never let colour be the only affordance of an interactive element in email. Underlines, weight and borders survive a recolour; a hex value does not.
  • Repeating a background colour on every nested surface looks like duplication and is actually what keeps the layout intact when a client drops one of them.

The app that sends these is on the download page, the help page covers what the alerts look like when they arrive, and the guide on how fast listings go is why none of this is worth getting wrong.