SOPs
Build It Light, Not Dark
The rule
Section titled “The rule”Anything rendered by something we do not control — emails above all — gets a light background. White card, dark text. No navy, no #1a1a2e, no dark blue.
This is not a style preference. It is a correctness rule. Dark-background HTML breaks in dark-mode renderers, and we do not get to choose the recipient’s renderer.
Why dark backgrounds break
Section titled “Why dark backgrounds break”Dark-mode renderers — Gmail’s dark theme, Apple Mail’s dark theme, Chrome’s “Auto Dark Mode for Web Contents” — color-invert HTML they decide is light-themed. The inversion is dumb: it flips light text toward black but leaves an already-dark background dark. The result is dark text on a dark background. Unreadable.
You cannot win this by making the text “whiter.” Every color you set gets inverted right back. The renderer is downstream of your CSS.
The tell
Section titled “The tell”If text renders dark but your source clearly says light, check the borders. In a normal render, dark 1px solid #333 dividers are nearly invisible on a dark card. If those dividers show up light in a screenshot while the text shows up dark, the whole page has been inverted. That is the fingerprint.
This cost three rebuild rounds on the Facebook follower report email on 2026-05-21 before the inversion was spotted — each round “made the text whiter,” each round got inverted straight back.
The reference pattern
Section titled “The reference pattern”The fixed fb-follower-tracker daily email is the canonical light template. build_email_html() in track.py:
| Element | Value |
|---|---|
| Card background | #ffffff |
| Card border | 1px solid #e2e8f0 |
| Body text | #1e293b |
| Headings | #0f172a |
| Secondary text | #64748b |
| Row / table panel | #f8fafc |
| Borders / dividers | #e2e8f0 |
| Positive metric | #15803d (green) |
| Negative metric | #b91c1c (red) |
| Root style hint | color-scheme: light |
color-scheme: light on the root tells conforming renderers the page is already finished — stop force-inverting it.
Light emails survive dark-mode clients because the client either leaves them alone or applies one coherent dark transform. Either way they stay readable. Dark emails get the broken half-transform.
Checklist — every email build
Section titled “Checklist — every email build”- Card background is
#ffffff(or a very light gray). Never navy, never#1a1a2e. - Body text is a dark slate (
#1e293b), headings darker (#0f172a). - Borders are light and visible on white (
#e2e8f0). - Status colors are dark enough to read on white —
#15803dgreen,#b91c1cred. The bright#4ade80/#f87171shades read fine on dark but wash out on white. color-scheme: lightis on the root element style.- Preview it, then send one real test to yourself and open it in your actual email client — that is the only renderer that matters.
- Emails: light, always. No exceptions.
- HTML reports, exported docs, anything that lands in a file someone opens elsewhere: light.
- Dashboards opened directly in our own Chrome (the
#1a1a2edashboard theme from the Markdown-to-HTML sync pattern): the dark theme stays — we control that renderer and force-dark is off there. If Chrome’s auto dark mode ever gets turned on, those invert too, and they move to light.
Related
Section titled “Related”- How We Build Anything — the baseline build checklist
- Notify James Pattern — how apps email alerts