maillint
v0.1.0
Published
Lint HTML email for client compatibility — locally, no render farm. Flags CSS/HTML that breaks in Outlook (Word engine), Gmail clipping at 102KB, missing image alt/dimensions and more, checked against a bundled caniemail-derived support matrix. Determinis
Maintainers
Readme
📬 maillint
Catch email rendering bugs before you hit send — Outlook breakage & Gmail clipping, linted locally.
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.htmlnewsletter.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 decorativeWhy 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 withoutrole="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 dependencyNode ≥ 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.jsonSee 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.md2. 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
--ailayer (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
