Skip to content

Repository files navigation

html-email logo

html-email logo

html-email

A cross-client HTML email framework that never drops support

Hand-authored, zero-dependency, and built on one rule: never abandon a client
while a single real person still uses it — from classic Outlook to dark mode.


CI OpenSSF Scorecard SLSA Build Level 3 Zero dependencies Node License: MIT ORCID

Most email frameworks quietly abandon the awkward clients — classic Outlook, Android 4's stock mail, Windows Phone — because supporting them is tedious. html-email does the opposite. It began in 2014 as "the most cross-compatible template you'll find anywhere," and it now covers both the 2014 client landscape and everything since: dark mode, the new Chromium Outlook, Apple Mail Privacy Protection, one-click unsubscribe, and the accessibility expectations of VoiceOver/TalkBack. There is no MJML and no compiler — the built output is the hand-tuned partials verbatim, so nothing is lost in translation on the oldest clients.

Why html-email

  • Never-drop-support. Every fix degrades gracefully; no workaround breaks an older client to help a newer one. 28 documented quirks, each defended in the markup.
  • Zero dependencies. A ~120-line build.mjs concatenates partials and injects content.json. No framework, no MJML, no fidelity loss. (Puppeteer is a dev-only dependency for screenshots.)
  • Verified two ways, not just authored. lint.mjs gates the HTML — Gmail's 102 KB clip and 8 KB <style> limit, bulletproof buttons, balanced MSO comments, alt text and image widths, dark-mode metas + Outlook DPI pinning, a real unsubscribe link, role="presentation" — and ships with its own self-test. Then npm run smoke renders every build in headless Chrome (light + dark) and fails on a broken image, horizontal overflow, or page error — the class of bug a HTML linter can't see.
  • Three ways to use it. Copy the single-file master, assemble the documented partials, or run the build. All three produce identical markup.
  • Dark mode, three ways. prefers-color-scheme, Outlook.com [data-ogsc]/[data-ogsb], and Gmail-safe explicit bgcolor on every cell.
  • Five branded templates, and a scaffolder. Distinct designs (see below), each rebuilt in the house conventions with self-authored generated art and passing both gates clean. Start a new one with node scripts/new-template.mjs <name> "Brand" — a complete, conformant template out of the box.

Preview

Framework — light Framework — dark Mobile Cairn Wellness template
Light Dark Mobile Cairn Wellness

One source, no per-client forks. Screenshots are the Blink render; see Testing for real-client verification. Live demo: https://dcondrey.github.io/html-email/

What's in the box

html-email/
├── framework/                 the maintained, modern template (start here)
│   ├── template.html          single-file master, fully commented
│   ├── partials/              the same template as documented components
│   ├── build/                 build.mjs (assembler) + lint.mjs + content.json
│   ├── dist/                  built output (email.html + email.min.html)
│   └── assets/                sample images
│
├── templates/                 distinct branded designs, house conventions
│   ├── cairn-wellness/         partials + manifest + content.json + assets + dist
│   ├── ferry-street-brewing/   brewery — 3-col tasting + gallery
│   ├── lantern-and-quill/      bookshop — team + product cards
│   ├── voltline/               electronics, dark-first — date-list + pricing table
│   └── meridian-advisory/      consulting — quote block
│
├── examples/
│   ├── northwind-botanicals/  the framework filled out as a real campaign
│   └── campaigns-2014/        original 2014 campaigns, kept as references
│
├── scripts/                   build-template.mjs, preview + asset generators
├── tools/                     email-checklist/ — self-contained pre-send checklist
├── docs/                      quirks reference, client matrix, testing, previews
└── legacy/                    the preserved, unmodified 2014 artifact

Quick start

Zero dependencies, Node ≥ 16.

1. Copy the single file. Open framework/template.html — the whole email in one commented file with {{placeholders}}. Replace them and ship.

2. Assemble the partials. framework/partials/ breaks it into components (header, hero, columns, button, footer, …). Paste the ones you need in order.

3. Build it.

cd framework/build
node build.mjs                 # inject content.json  → ../dist/email.html
node build.mjs --production    # + minify (strips docs, keeps MSO comments)
node lint.mjs ../dist/email.html

Build a branded template the same way:

node scripts/build-template.mjs templates/cairn-wellness
node framework/build/lint.mjs templates/cairn-wellness/dist/email.html

Two layers of checks. lint.mjs validates the HTML (Gmail size, dark-mode metas, bulletproof buttons, image widths, …). npm run smoke validates the render: it loads every built email in headless Chrome (light and dark) and fails on a broken image, horizontal overflow, or a page error — the class of bug lint cannot see. npm test runs the build + lint + the linter's own self-test; CI additionally runs the smoke-test.

Templates

Template Design Status
framework/ The canonical modern template (header · hero · columns · button · footer) ✅ Maintained
examples/northwind-botanicals/ The 2014 production reference, scrubbed to a fictitious brand
templates/cairn-wellness/ Wellness shop (green/cream/amber, Poppins)
templates/ferry-street-brewing/ Brewery (amber/brown on dark roast, Oswald · 3-col tasting + gallery)
templates/lantern-and-quill/ Bookshop / education (coral+yellow on plum, Playfair · team + product cards)
templates/voltline/ Electronics sale, dark-first (dark+pink, Space Grotesk · date-list + pricing table)
templates/meridian-advisory/ Consulting / corporate (charcoal+coral on grey, IBM Plex Sans · quote block)
Cairn Wellness Ferry Street Brewing Lantern & Quill Voltline Meridian Advisory
Cairn Wellness Ferry Street Brewing Lantern & Quill Voltline Meridian Advisory

Brand names and imagery are fictitious mock content for demonstration; imagery is self-authored, generated by scripts/.

Start a new one: node scripts/new-template.mjs <kebab-name> "Brand Name" scaffolds a complete template (partials, manifest, content.json, asset-generator stub) that builds and lints 0/0 out of the box. Retheme the palette in 00-document-open.html, edit the copy, fill in the asset generator, then wire it into package.json and the CI/release workflows.

Supply chain & provenance

Actions are pinned to full commit SHAs, and CodeQL, Dependency Review, OpenSSF Scorecard, and Dependabot run in CI. Tagged releases publish a signed SLSA build-provenance attestation over the release archive (see .github/workflows/release.yml); verify it with:

gh attestation verify html-email-<tag>.zip --repo dcondrey/html-email

Security policy and private reporting: SECURITY.md.

Documentation

License

MIT — use it, adapt it, ship it. Attribution appreciated, not required. © 2026 David Condrey.

About

Zero-dependency, hand-authored HTML email framework that never drops support for the oldest clients — MJML alternative with 28 documented quirks.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Sponsor this project

Contributors

Languages