Emailens
Engine Package

Dark Mode Simulation

Simulate how email clients render your email in dark mode.

simulateDarkMode

Generates a dark mode variant of the transformed HTML for a specific client.

function simulateDarkMode(
  html: string,
  clientId: string
): { html: string; warnings: CSSWarning[] }

Parameters

ParameterTypeDescription
htmlstringTransformed HTML (output of transformForClient)
clientIdstringClient ID to simulate dark mode for

Returns

FieldTypeDescription
htmlstringHTML with dark mode CSS applied
warningsCSSWarning[]Dark-mode-specific warnings

Example

import { transformForClient, simulateDarkMode } from "@emailens/engine";

const transform = transformForClient(emailHtml, "gmail-web");
const darkMode = simulateDarkMode(transform.html, "gmail-web");

console.log(darkMode.warnings.length); // Dark-mode-specific issues

Dark mode support by client

ClientDark modeBehavior
Gmail WebYesUI-only (no content inversion)
Gmail iOSYesFull color inversion
Gmail AndroidYesPartial color inversion
Outlook 365YesPartial color inversion
Outlook (New)YesPartial color inversion
Outlook ClassicYesFull color inversion
Apple Mail (all)YesRespects prefers-color-scheme; partial inversion if absent
Yahoo MailYesUI-only (no content inversion)
Samsung MailYesRespects prefers-color-scheme; partial inversion if absent
ThunderbirdYesRespects prefers-color-scheme (v140+); full inversion if absent

For clients with UI-only dark mode (Gmail Web, Yahoo Mail), simulateDarkMode applies body wrapper styling but does not invert email content colors.

How simulation works

Emailens simulates dark mode by:

  1. Inverting light backgrounds to dark equivalents
  2. Adjusting text colors for contrast
  3. Checking for prefers-color-scheme: dark media queries (clients that respect them skip forced inversion when present)
  4. Applying client-specific dark mode behaviors (full vs partial inversion, UI-only darkening)

Grading contrast in the dark render

Simulating the render is half of it. checkDarkModeContrast(html) grades the text contrast of the result, which is where the failure actually shows up.

There are two ways an email breaks in dark mode, and they need separate checks:

  • Clients that force an inversion. Gmail Android inverts partially and Gmail iOS fully, and they disagree: a colour just under Android's lightness threshold is left alone there while the surface behind it is repainted. checkDarkModeContrast runs both.
  • The email's own dark block. checkDarkStylesContrastFromDom($) applies the email's @media (prefers-color-scheme: dark) rules the way Apple Mail, Superhuman and Thunderbird would. A dark block that repaints a card but does not re-colour the text sitting on it lands at 1:1, and no light-mode check can see it.

auditEmail merges both into report.darkContrast, deduplicated per element.

Note: Previews are simulated approximations rendered in Chromium. Actual rendering may differ in native email clients due to engine-specific quirks (e.g., Outlook's Word engine, Apple Mail's WebKit).

On this page