Skip to main content
Emailens
Blog

How to Test HTML Emails: The Developer's QA, Preview & Automation Guide

Lint the HTML for client support, preview it in the clients your readers use, check its size, links and accessibility, and run the same checks in CI so a regression fails before it ships.

The checker needs a computer screen.

A free account opens all 21 clients and keeps your checks for 7 days. Create a free account →

Philippe Kam, founder of Emailens · Published 6 Oct 2026 · Updated 16 Oct 2026

@emailens/engine 0.16.1 · data last changed 17 Oct 2026

1. The Flawed Ritual: Why Sending Test Emails to Yourself Fails

Every frontend developer who touches transactional or marketing emails has gone through the same ritual: you write the template, send a test email to your personal Gmail inbox, open it in Chrome, glance at it on your phone, and decide it is ready to ship.

Then the campaign sends, and support tickets pour in:

  • Outlook 365 on Windows blew out your two-column grid into 1,200px of horizontal chaos.
  • Gmail Android inverted your dark brand logo into an illegible dark-gray smudge.
  • Windows Surface users are complaining about white hairline slashes slicing through hero photography.
  • Gmail clipped the bottom half of the email, severing the unsubscribe button and triggering spam compliance flags.

Sending a test email to your personal inbox gives a dangerous illusion of safety. Five hidden traps undermine this approach:

Forwarding Mutates the DOM

When you forward a test email to a colleague to check their Outlook client, your webmail client re-serializes the markup, strips <style> blocks, and wraps content in proprietary blockquotes. You are testing a corrupted forward, not your raw email.

Mac Outlook is Not Windows Outlook

If you develop on macOS, testing in Outlook for Mac tests Apple WebKit. It will happily render modern CSS flexbox and border-radius. Meanwhile, enterprise recipients on Windows run classic Outlook powered by the Microsoft Word 2007-2021 HTML engine.

DPI Scaling Artifacts are Invisible at 1x

On standard 96 DPI displays, table borders align cleanly. On 125% (120 DPI) or 150% (144 DPI) Windows laptop displays, Word recalculates fractional pixels, creating 1px hairline gaps unless DPI settings are explicitly declared.

Aggressive Browser Caching Masks Asset Bugs

Your local browser aggressively caches remote stylesheets and hero images. External recipients encountering cold CDNs, rate limits, or missing CORS headers may see missing imagery that never failed on your local machine.

2. The 4-Layer Email Testing Pyramid

Web development evolved past manual QA decades ago through unit, integration, and end-to-end test pyramids. Email development requires the exact same structured discipline.

A comprehensive email testing strategy is built across four distinct layers:

Layer 1

Static AST Linting & Spec Verification (<100ms)

Analyze the compiled HTML and CSS abstract syntax tree without launching a browser. Check CSS property support against client matrices, verify file payload under 102KB, and validate accessibility standards.

Layer 2

Local Developer Sandbox & Viewport Emulation (<1s)

Real-time local previews during template development. Test responsive breakpoints (320px mobile to 600px desktop table caps), inspect layout reflows, and simulate dark mode color inversions with instant hot reload.

Layer 3

Cross-Client Engine Virtualization & Visual Diffs (5-15s)

Render your templates inside actual rendering engines: Microsoft Word MSO, WebKit (Apple Mail), and Blink (Gmail). Generate pixel-level visual regression screenshots between git commits.

Layer 4

Deliverability, Spam & Header Pre-Flight (10-30s)

Validate infrastructure headers before launch: SPF, DKIM, DMARC record alignment, RFC 8058 One-Click Unsubscribe compliance, SpamAssassin heuristics, and plain-text MIME part parity.

3. Layer 1: Static AST Analysis & Rule Linting

The fastest bug to fix is the one caught before firing up a render engine. Static AST analysis inspects your raw compiled HTML and CSS against a database of known client quirks.

Key checks that run in under 100 milliseconds:

  • CSS Feature Support: Flagging properties known to fail in major clients (such as display: flex in Outlook, position: absolute in Gmail, or unsupported CSS functions like clamp()). Check unsupported properties instantly with our CSS Compatibility Checker.
  • Byte Payload Ceiling: Gmail truncates messages exceeding 102KB of raw HTML text. Measuring your payload with an Email Size Checker prevents clipping before deployment. Read our guide on why Gmail clips emails for mitigation strategies.
  • Accessibility & Table Semantics: Ensuring every layout table includes role="presentation" and cellpadding="0", validating image alt text, and computing WCAG 2.1 AA text contrast with our Accessibility Checker.
  • MSO Conditional Balance: Detecting unclosed conditional comments or missing Outlook ghost table tags that cause Outlook to swallow surrounding markup.
scripts/lint-email.ts
32 lines
// scripts/lint-email.ts
import { readFileSync } from "node:fs";
import { analyzeEmail, checkSize, checkAccessibility } from "@emailens/engine";

const html = readFileSync("./dist/newsletter.html", "utf-8");

// 1. Check strict 102KB payload limit to prevent Gmail clipping
const sizeReport = checkSize(html);
if (sizeReport.sizeBytes > 100 * 1024) {
  console.error(`[FAIL] Payload exceeds 100KB: ${sizeReport.sizeBytes} bytes. Gmail will clip!`);
  process.exit(1);
}

// 2. Validate CSS compatibility against 150+ email client rules
const { issues, score } = analyzeEmail(html);
const criticalErrors = issues.filter((i) => i.severity === "error");

if (criticalErrors.length > 0) {
  console.error(`[FAIL] Found ${criticalErrors.length} critical rendering errors (Score: ${score}/100):`);
  for (const err of criticalErrors) {
    console.error(`  - [${err.client}] ${err.property}: ${err.message}`);
  }
  process.exit(1);
}

// 3. Audit accessibility (WCAG 2.1 AA contrast & table role)
const a11yReport = checkAccessibility(html);
if (a11yReport.issues.length > 0) {
  console.warn(`[WARN] ${a11yReport.issues.length} accessibility warnings found.`);
}

console.log(`[PASS] Email passed AST linting. Compatibility score: ${score}/100.`);

4. Layer 2: Local Developer Sandbox & Responsive Emulation

Whether you build with React Email, MJML, or hand-coded HTML, local rapid previewing is where developer velocity lives. Waiting minutes for a cloud screenshot on every CSS tweak destroys productivity.

The Critical Viewport Test Matrix

Unlike web pages that stretch smoothly, email layout relies on fixed table caps and fluid hybrid blocks. Always test these four exact dimensions:

320px (Compact Mobile)

iPhone SE / small Android devices. Catches multi-column blocks that fail to stack, unscaled images overflowing the viewport, and tiny 10px tap targets.

375px - 414px (Standard Mobile)

iPhone 14/15/16 and modern Samsung Galaxy viewports. Verifies that stacked buttons span comfortably and typography scales legibly.

600px - 640px (Standard Desktop Container)

The universal email width limit. Verifies that ghost tables, side-by-side columns, and hero images honor max-width boundaries without blowing out.

1200px+ (Wide Screen Webmail)

Webmail inboxes on wide monitors (Gmail, Outlook.com). Checks that the email body centers cleanly with appropriate background letterboxing rather than clinging to the left wall.

Dark Mode Inversion Simulation

Modern email clients do not handle dark mode uniformly. Apple Mail respects your CSS media queries, but Gmail Android and Outlook on Windows run algorithmic color inversions.

In your local sandbox, test both modes using our Dark Mode Simulator. Verify that transparent PNG logos carry a subtle light stroke or glow, preventing them from disappearing against dark backgrounds. For complete CSS strategies, see our Dark Mode Email CSS Guide.

5. Layer 3: Cross-Client Engine Virtualization (Word vs Blink vs WebKit)

Static linting and browser sandboxes catch 80% of bugs, but the remaining 20% stem from proprietary quirks in real email client engines.

The email rendering ecosystem is divided into three fundamental engine families:

1. Microsoft Word MSO (Classic Outlook on Windows)Highest Risk

The Microsoft Word engine powers Outlook 2016, 2019, 2021, and classic enterprise Microsoft 365. It ignores CSS max-width, flexbox, background images, and border-radius. All background imagery requires VML v:rect and v:fill markup, and column layouts require MSO ghost tables.

2. Blink + Google Sanitizer (Gmail Web & Mobile)Medium Risk

Gmail runs Chromium under the hood, but subjects incoming HTML to an aggressive server-side sanitizer. Gmail strips entire <style> blocks if a single syntax error is found, strips CSS variables, rewrites class names, and clamps at 102KB. See our breakdown of email rendering bugs across engines.

3. Apple WebKit (Apple Mail iOS, iPadOS & macOS)Lowest Risk

Apple Mail uses Safari WebKit. It supports CSS grid, flexbox, CSS variables, smooth web fonts, and native dark mode queries. However, Apple Mail Privacy Protection (MPP) caches and proxies images, affecting real-time dynamic countdown timers.

Why Browser Emulation Cannot Replace Real Word Engines: Many developers attempt to simulate Outlook in Chrome using CSS overrides. This is fundamentally impossible. Word does not run a modified browser engine: it uses a legacy print layout parser with custom table border-collapse algorithms, DPI rounding logic, and vector coordinate systems. Real engine virtualization is indispensable.

6. Automated CI/CD Regression Testing in GitHub Actions

Email templates should be tested with the same automated rigor as your web application. Introducing a broken CSS property or an unoptimized 2MB hero banner should block a pull request before it merges to main.

Here is how to wire automated email assertions into your GitHub Actions workflow:

.github/workflows/email-qa.yml
33 lines
# .github/workflows/email-qa.yml
name: Email QA & Rendering Regression

on:
  pull_request:
    paths:
      - 'emails/**'
      - 'packages/transactional/**'

jobs:
  test-emails:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest

      - name: Install Dependencies
        run: bun install --frozen-lockfile

      - name: Build Email Templates
        run: bun run build:emails

      - name: Run Static AST Email Linter
        run: bun run test:email-lint

      - name: Visual Snapshot & Regression Test
        run: bun run test:email-snapshots
        env:
          EMAILENS_API_KEY: ${{ secrets.EMAILENS_API_KEY }}

Automated Visual Snapshot Testing with Playwright

You can pair static linting with headless Playwright visual regression tests. The runner renders the compiled email at 375px and 1200px viewports, verifying that elements stay within table bounds and visual diffs do not exceed 1% threshold:

tests/email-snapshots.test.ts
36 lines
// tests/email-snapshots.test.ts
import { test, expect } from "@playwright/test";
import { readFileSync } from "node:fs";

test.describe("Transactional Email Visual Snapshots", () => {
  const html = readFileSync("./dist/welcome-email.html", "utf-8");

  test("renders correctly on mobile viewport (375px)", async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 812 });
    await page.setContent(html, { waitUntil: "networkidle" });

    // Ensure horizontal scrolling does not occur
    const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
    expect(scrollWidth).toBeLessThanOrEqual(375);

    // Capture visual snapshot
    await expect(page).toHaveScreenshot("welcome-email-mobile.png", {
      maxDiffPixelRatio: 0.01,
    });
  });

  test("renders correctly on desktop container (600px)", async ({ page }) => {
    await page.setViewportSize({ width: 1200, height: 900 });
    await page.setContent(html, { waitUntil: "networkidle" });

    // Assert main container does not blow out past 600px
    const containerWidth = await page.locator("table[align='center']").first().evaluate(
      (el) => el.getBoundingClientRect().width
    );
    expect(containerWidth).toBeCloseTo(600, 1);

    await expect(page).toHaveScreenshot("welcome-email-desktop.png", {
      maxDiffPixelRatio: 0.01,
    });
  });
});

7. The 2026 Pre-Send QA Checklist (15-Point Field Manual)

Before pushing any transactional template to production or hitting send on a major marketing broadcast, verify each item on this battle-tested checklist:

Category 1: Identity, Headers & Deliverability
From & Reply-To Alignment: Validated sender domain has SPF, DKIM, and DMARC alignment. Review our Email Deliverability Guide.
RFC 8058 One-Click Unsubscribe: Declared List-Unsubscribe: <https://...> and List-Unsubscribe-Post: List-Unsubscribe=One-Click headers.
Preheader Preview Text: High-intent preheader text followed by non-breaking space / zero-width joiner padding to prevent footer text from bleeding into the inbox preview.
Category 2: Layout & Cross-Client Compatibility
Outlook Ghost Table Enclosure: Multi-column fluid rows are enclosed in MSO conditional tables with explicit widths.
High-DPI 96 PPI Declaration: Header includes <o:PixelsPerInch>96</o:PixelsPerInch> to prevent 1px hairline cracks on high-res Windows displays.
Bulletproof VML Backgrounds: Hero banners with background images include VML fallback shapes for desktop Outlook. Generate markup with our VML Generator.
Category 3: Media, Typography & Images
Retina 2x with Explicit Dimensions: Images are rendered at double resolution with explicit HTML width and height attributes to prevent layout shift.
Alt Text & Fallback Background Colors: All images include descriptive alt text and container background colors for when image loading is disabled.
Font Fallback Stack: Web fonts include bulletproof fallbacks (e.g., font-family: 'Inter', -apple-system, BlinkMacSystemFont, Arial, sans-serif) to prevent Outlook from falling back to Times New Roman.
Category 4: Dark Mode & Accessibility
WCAG 2.1 AA Contrast Ratios: Text meets minimum 4.5:1 contrast against both light and inverted dark backgrounds.
Transparent Logos with Dark Mode Halos: Dark brand logos on transparent backgrounds have subtle light borders to stay visible under full inversion.
Screen Reader Table Roles: All layout tables carry role="presentation". See our Email Accessibility Guide.
Category 5: Payload Size & Plain-Text Fallback
Payload Under 100KB: Raw HTML text is safely below Gmail's 102KB clipping threshold.
Synchronized Plain-Text MIME Part: A clean text/plain multipart alternative is generated and bundled with all links clearly displayed.
Link Reachability & Tracking: Zero placeholder href="#" links, and UTM parameters are properly appended and reachable.

8. Legacy Screenshot Farms vs Modern Developer Sandboxes

Historically, the only way to test emails was subscribing to legacy screenshot testing suites like Litmus or Email on Acid. While they pioneered the industry, their architecture was designed for manual marketing operations, not modern engineering teams.

FeatureLegacy Screenshot FarmsEmailens Developer Platform
Test Feedback Loop5 to 15 minutes per test queueSub-second AST + 5s engine renders
CI/CD Pipeline IntegrationSlow, expensive, often rate-limitedFirst-class GitHub Actions & CLI support
AST Code Analysis & FixesStatic screenshots with generic articlesExact line-level AST errors & AI fix prompts
Local Dev Hot ReloadingNo local development runtimeInstant local sandbox with viewport controls
Pricing Structure$99 to $300+/month per userDeveloper-friendly transparent pricing

For a detailed architectural comparison, read our in-depth analysis of Emailens vs Litmus and Emailens vs Email on Acid.

Questions

Why is sending a test email to my personal Gmail account insufficient?

Testing in your personal inbox checks only one browser and one display density. Forwarding the email to coworkers mutates the DOM, strips style blocks, and introduces quote wrapper divs. Furthermore, it completely fails to test the desktop Microsoft Word engine (used by Outlook on Windows), dark mode inversion logic, and high-DPI scaling artifacts.

What is the 4-layer email testing pyramid?

A modern email testing pipeline organizes tests into four layers: Layer 1 is Static AST Analysis (linting CSS, checking byte weight, and validating HTML structure in under 100ms); Layer 2 is Local Sandboxing (rapid viewport and dark mode emulation during development); Layer 3 is Real-Engine Virtualization (verifying pixel fidelity across Word, Blink, and WebKit); and Layer 4 is Deliverability & Mailbox Verification (checking SPF, DKIM, DMARC, and spam scores).

How do I automate email rendering tests in CI/CD pipelines?

You can run automated headless test scripts in GitHub Actions using tools like Playwright or the Emailens CLI. The runner renders compiled email templates, asserts that the raw HTML payload is under 100KB, validates WCAG contrast ratios, checks link reachability, and compares visual snapshots against approved branch baselines.

Why do legacy email testing tools take 10 to 15 minutes to run?

Legacy cloud screenshot services spin up full desktop virtual machines, open desktop clients, trigger manual screen captures, and upload images. Modern developer testing platforms combine AST-level static analysis with headless engine virtualization, returning compatibility diagnostics and rendering previews in seconds.

At what file size does Gmail truncate HTML emails?

Gmail strictly truncates emails when the raw HTML payload exceeds 102KB (102,400 bytes). When clipped, Gmail replaces the rest of the message with a '[Message clipped] View entire message' link, which breaks tracking pixels and hides legally mandated unsubscribe links.

Your next question

Also useful: CSS Compatibility Checker · Email Rendering Playground · Email Spam Checker

Paste the email that broke.

The checker needs a computer screen.

Get new email CSS fixes by email, or follow by RSS.