Skip to content

didrod205/maillint

Repository files navigation

📬 maillint

Catch email rendering bugs before you hit send — Outlook breakage & Gmail clipping, linted locally.

npm version bundle size CI types license

You hand-code (or template) an HTML email, send a test to yourself, it looks fine — and then it ships broken in Outlook (which renders with Word's engine and ignores flex, position, background-image, border-radius…) or clipped in Gmail (which truncates anything past ~102 KB, hiding your CTA and unsubscribe link). The only ways to know are a paid render farm (Litmus, Email on Acid) or manually cross-checking every CSS property on caniemail.com.

maillint checks your email HTML against a bundled, caniemail-derived support matrix — locally, deterministically, in one command. No render farm, no API key, no sending.

npx maillint scan newsletter.html
newsletter.html  0/100 (F) · 1.3 KB
  ✗ L9   display: flex            breaks in: Gmail, Outlook (Windows), Outlook.com
  ✗ L11  CSS background-image     breaks in: Outlook (Windows, Word engine)
  ✗ L13  border-radius            breaks in: Outlook (Windows, Word engine)
  ✗ L44  <svg> graphics           breaks in: Gmail, Outlook, Yahoo!, Samsung…
  ✗ L46  <form> interactivity     breaks in: Gmail, Outlook, Outlook.com, Yahoo!
  ⚠ L35  Image without alt text   → add alt, or alt="" for decorative

Why maillint?

  • 🎯 Per-client truth, not guesses. Each finding names the exact clients that break and how to fix it, from a curated matrix modelled on caniemail.com — the same data the pros check by hand, baked in.
  • 🔒 Local & deterministic. No render farm, no upload, no API key. Same input → same output. Runs offline and in CI on every template change.
  • 📏 Gmail clipping is just math. maillint measures the real UTF-8 byte size and warns before you cross the ~102 KB line that hides your call to action.
  • a11y built in. Missing image alt, width-less images (Outlook blows them up), layout tables without role="presentation", missing charset/doctype/lang.
  • 🪶 Zero-dependency core. The library imports nothing at runtime; the CLI adds only cac + picocolors.

Why not just ask an LLM "will this render in Outlook"? Client support is exact, shifting data — a chatbot hallucinates it, can't run in CI, and can't byte-count your template on every commit. maillint is a lookup table, not a vibe.

Install

# run it now, no install
npx maillint scan email.html

# or add it
npm install -g maillint      # global CLI
npm install -D maillint      # CI dependency

Node ≥ 18. Ships ESM + CJS + TypeScript types. Works on raw HTML, or the HTML your MJML/Handlebars/React-Email build emits.

Quick start

maillint scan newsletter.html              # lint one file
cat email.html | maillint scan             # or pipe it
maillint scan ./emails --min-score 80      # CI gate over a folder
maillint scan email.html --clients gmail,outlook-windows   # only the clients you target
maillint scan email.html --md report.md    # Markdown report for a PR
maillint init                              # write maillint.config.json

See examples/sample-report.md for a full report, and examples/clean.html for an email that scores 100/100.

What it checks

Category Examples
Client compatibility flex/grid, position, max-width, border-radius, box-shadow, background-image, transform/animation, web fonts (@font-face), @import, @media (Outlook), padding on <a>, <form>/<input>/<button>, <svg>, <video> — each mapped to the clients that break
Gmail clipping real UTF-8 byte size vs Gmail's ~102 KB clip limit (error over, warning approaching)
Images missing alt (clients block images by default), missing explicit width (Outlook scaling)
Structure & a11y <!DOCTYPE>, <meta charset>, <html lang>, <title>, role="presentation" on layout tables

Each finding is a weighted error / warning / info; files roll up to a 0–100 score and an A–F grade you can gate in CI.

Real scenarios

1. CI gate on your email templates. A PR that adds a display:flex hero or a web font fails the build before it reaches a real inbox:

# .github/workflows/email.yml
- run: npx maillint scan ./emails --min-score 85 --md email-report.md

2. Lint your ESP/MJML output. Point maillint at the compiled HTML from React Email, MJML or your ESP to catch what the framework (or a hand-tweak) let through — clipping, a missing alt, an unsupported gradient.

3. Target only the clients you support. A B2B product whose audience lives in Outlook? --clients outlook-windows,outlook-com focuses the report on what actually matters to your recipients.

Configuration

maillint init writes maillint.config.json:

{
  "clients": ["apple-mail-ios", "gmail", "outlook-windows", "..."],
  "ignore": [],            // rule ids, e.g. ["compat.border-radius"]
  "partialSeverity": "info",
  "clipBytes": 102400,      // Gmail clipping threshold
  "clipWarnRatio": 0.9,
  "minScore": 0             // CI gate
}

Library API

import { lintEmail, DEFAULT_CONFIG } from "maillint";

const { findings, bytes } = lintEmail(html, DEFAULT_CONFIG);
for (const f of findings) {
  console.log(f.severity, f.rule, f.unsupported); // e.g. ["gmail","outlook-windows"]
}

Also exported: lintFile, buildReport, parseEmail, the support matrix (CSS_FEATURES, HTML_FEATURES, AT_RULE_FEATURES), CLIENTS, and all types. The core is dependency-free and browser-safe (great for a live playground).

Roadmap

  • 🤖 Optional --ai layer (bring-your-own key) to suggest rewrites for flagged code (e.g. a VML fallback for a CSS background). The core stays 100% offline and deterministic — AI is enhancement only.
  • More matrix coverage (dark-mode prefers-color-scheme, mso- props, AMP4email).
  • Inline <!-- maillint-disable --> comments and per-rule severity overrides.
  • Auto-inlining hints (which <style> rules to inline for stripping clients).
  • A web playground — paste HTML, see the report, nothing uploaded.

💖 Sponsor

maillint is free and MIT-licensed, built and maintained in spare time. If it saved you a broken send (or a render-farm subscription), please consider supporting it:

  • Star this repo — the simplest free way to help others find it.
  • 🍋 Sponsor via Lemon Squeezy — one-time or recurring.

The support matrix is modelled on the excellent community data at caniemail.com. maillint bundles a curated snapshot; it isn't affiliated with caniemail.

License

MIT © maillint contributors

About

Lint HTML email for client compatibility — locally, no render farm. Flags CSS/HTML that breaks in Outlook (Word engine) & Gmail clipping at 102KB, against a bundled caniemail-derived support matrix. Deterministic CLI, JSON/MD reports.

Topics

Resources

License

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors