When I built emailens.dev (full story here), I made a decision early on: the core analysis engine should be open source. The SaaS adds screenshots, AI fixes, and a dashboard on top, but the fundamental email QA capabilities - CSS compatibility analysis, spam scoring, accessibility checking - should be available to every developer.
The result is three npm packages: @emailens/engine, @emailens/mcp, and @emailens/cli.
@emailens/engine - The Core
The engine is the foundation everything else builds on. One function call, eight analysis types:
import { auditEmail } from '@emailens/engine';
const report = await auditEmail(html, {
framework: 'jsx', // or 'mjml', 'maizzle', 'html'
});
// Returns:
// report.css - Per-client CSS compatibility warnings
// report.scores - 0-100 scores for each of 12 clients
// report.spam - Spam scoring (45+ rules)
// report.links - Broken hrefs, insecure links
// report.accessibility - WCAG contrast, alt text, heading hierarchy
// report.images - Missing dimensions, oversized data URIs
// report.inbox - Subject/preheader truncation per client
// report.templates - Unresolved merge tagsCSS Compatibility: 251 Features x 12 Clients
The heart of the engine is a support matrix tracking 251 CSS and HTML features across 12 email clients:
const CSS_SUPPORT: Record<string, Record<string, SupportLevel>> = {
'<style>': { 'gmail-web': 'unsupported', 'outlook-windows': 'supported', ... },
'display:flex': { 'gmail-web': 'unsupported', 'outlook-windows': 'unsupported', ... },
'border-radius': { 'gmail-web': 'supported', 'outlook-windows': 'unsupported', ... },
'max-width': { 'gmail-web': 'unsupported', 'apple-mail-macos': 'supported', ... },
// ... 247 more features
};The data is synced from caniemail.com and verified against real client testing. Support levels are supported, partial, unsupported, or unknown.
The analysis engine parses HTML with Cheerio and CSS with css-tree, then cross-references every property against the matrix:
function analyzeEmailFromDom($: CheerioAPI, framework?: string): CSSWarning[] {
const warnings: CSSWarning[] = [];
// Parse <style> blocks
$('style').each((_, el) => {
const ast = csstree.parse($(el).text());
csstree.walk(ast, {
visit: 'Declaration',
enter(node) {
const property = node.property;
for (const client of EMAIL_CLIENTS) {
if (CSS_SUPPORT[property]?.[client.id] === 'unsupported') {
warnings.push({
property,
client: client.id,
severity: 'error',
fix: getFixSnippet(property, client.id, framework),
});
}
}
}
});
});
// Scan inline styles
$('[style]').each((_, el) => {
parseAndCheckInlineStyles($(el).attr('style'), warnings);
});
return warnings;
}Fix Snippets Are Framework-Aware
When the engine detects an unsupported property, the fix suggestion matches the framework you're using:
For display: flex in Outlook:
- HTML: Convert to
<table><tr><td>layout with VML fallbacks - React Email (JSX): Use
<Row>and<Column>components - MJML: Use
<mj-section>and<mj-column> - Maizzle: Use Tailwind table utility classes
This is what makes Emailens different from raw caniemail data - you don't just learn what's broken, you get a fix in the syntax you're already writing.
Session API: Parse Once, Analyze Many
The session API eliminates redundant DOM parsing when running multiple analysis types:
import { createSession } from '@emailens/engine';
const session = createSession(html, { framework: 'jsx' });
// All methods share the same parsed DOM
const warnings = session.analyze(); // CSS compatibility
const scores = session.score(warnings); // Per-client scores
const spam = session.analyzeSpam(); // Spam scoring
const links = session.validateLinks(); // Link validation
const a11y = session.checkAccessibility(); // Accessibility audit
// Transforms create isolated copies (they mutate)
const gmailHtml = session.transformForClient('gmail-web');
const allClients = session.transformForAllClients();Spam Scoring
The spam analyzer checks 45+ signals modeled after SpamAssassin, CAN-SPAM, and GDPR standards:
const spamReport = session.analyzeSpam();
// {
// score: 3.2, // 0-10 scale, lower is better
// signals: [
// { rule: 'ALL_CAPS_SUBJECT', points: 1.5, description: 'Subject line is all caps' },
// { rule: 'MISSING_UNSUBSCRIBE', points: 1.0, description: 'No unsubscribe link found' },
// { rule: 'EXCESSIVE_IMAGES', points: 0.7, description: 'Image-to-text ratio too high' },
// ]
// }Deliverability Checking (Server Module)
The engine includes a server-only module for DNS-based deliverability checks:
import { checkDeliverability } from '@emailens/engine/server';
const result = await checkDeliverability('yourdomain.com');
// {
// spf: { status: 'pass', record: 'v=spf1 include:_spf.google.com ~all' },
// dkim: { status: 'pass' },
// dmarc: { status: 'fail', reason: 'No DMARC record found' },
// mx: { status: 'pass', records: ['alt1.gmail-smtp-in.l.google.com'] },
// bimi: { status: 'none' },
// }This is a separate subpath export (@emailens/engine/server) because it requires Node.js DNS APIs and shouldn't be bundled in browser/edge environments.
The Numbers
- 251 CSS/HTML features tracked across 12 clients
- 45+ spam signals in the scoring engine
- 574 passing tests with Bun's test runner
- Zero runtime dependencies beyond Cheerio and css-tree
- MIT licensed - use it however you want
@emailens/mcp - Claude Desktop Integration
The MCP (Model Context Protocol) server turns Claude Desktop into an email QA expert. Instead of copying email HTML back and forth, you talk to Claude and it runs Emailens analysis directly:
You: "Check if this email works in Outlook"
Claude: [runs preview_email] "Your email scores 74/100 in Outlook Windows.
3 errors: display:flex, border-radius, max-width.
Here are the fixes..."
Five Tools
The MCP server exposes 5 tools:
1. preview_email - Full compatibility preview across all 12 clients:
{
"html": "<html>...</html>",
"format": "jsx",
"darkMode": true
}Returns per-client scores, CSS warnings, dark mode issues, and framework-specific fix snippets.
2. audit_email - Comprehensive quality audit in one call:
{
"html": "<html>...</html>",
"skip": ["images"]
}Runs CSS compatibility, spam scoring, link validation, accessibility audit, and image analysis. The skip parameter lets you exclude checks you don't care about.
3. analyze_email - Quick CSS-only analysis (faster than full preview):
{
"html": "<html>...</html>",
"clients": ["gmail-web", "outlook-windows"]
}Returns warnings and scores for specific clients.
4. fix_email - Generate structured fix prompts:
{
"html": "<html>...</html>",
"format": "mjml",
"scope": "current"
}Classifies warnings as CSS-only or structural, estimates token usage, and returns a markdown prompt ready for AI application.
5. list_clients - Enumerate all 12 supported clients with metadata (category, engine, dark mode support).
Installation
npm install -g @emailens/mcpAdd to Claude Desktop's MCP config:
{
"mcpServers": {
"emailens": {
"command": "npx",
"args": ["@emailens/mcp"]
}
}
}That's it. Claude Desktop now has email QA superpowers.
@emailens/cli - CI/CD Email Linting
The CLI brings email QA into your build pipeline. The killer feature is the lint command with structured exit codes for CI:
# Lint an email template
emailens lint ./emails/welcome.html --fail-on error
# Exit codes:
# 0 = No issues
# 1 = Errors found (fail the build)
# 2 = Warnings only (configurable)Commands
analyze - CSS compatibility analysis with per-client scores:
emailens analyze ./emails/welcome.html
# ┌─────────────────┬───────┬────────┬──────────┬──────┐
# │ Client │ Score │ Errors │ Warnings │ Info │
# ├─────────────────┼───────┼────────┼──────────┼──────┤
# │ Gmail Web │ 85 │ 1 │ 2 │ 0 │
# │ Outlook Windows │ 62 │ 3 │ 4 │ 1 │
# │ Apple Mail │ 98 │ 0 │ 0 │ 1 │
# └─────────────────┴───────┴────────┴──────────┴──────┘preview - Full preview with dark mode simulation and optional screenshots:
emailens preview ./emails/welcome.html --dark-mode --screenshotsaudit - Comprehensive audit (CSS + spam + links + accessibility + images):
emailens audit ./emails/welcome.mjml --jsonfix - AI-powered fixes using Claude (requires ANTHROPIC_API_KEY):
emailens fix ./emails/welcome.jsx --estimate # Show token estimate first
emailens fix ./emails/welcome.jsx # Apply fixesexport - Generate self-contained HTML or JSON reports:
emailens export ./emails/welcome.html --format html --output report.htmlMulti-Format Support
The CLI auto-detects file format from the extension:
.html/.htm→ Raw HTML.jsx/.tsx→ React Email (compiled with @react-email/render).mjml→ MJML (compiled with mjml).htmlin a Maizzle project → Maizzle
You can also pipe from stdin:
cat emails/welcome.html | emailens analyze -GitHub Action
The CLI powers a GitHub Action for automated email QA on pull requests:
- name: Email QA
uses: emailens/action@v1
with:
files: 'emails/**/*.html'
fail-on: errorWhy Open Source?
Three reasons:
1. Developer trust. Email rendering is frustrating enough without trusting a black box. Developers can read the engine source, verify the CSS support data, and understand exactly what's being checked. The 574 tests are public - you can see the edge cases being handled.
2. Ecosystem integration. By publishing the engine on npm, other tools can embed email QA: editor plugins, linter integrations, build tools, other SaaS platforms. The MCP server and CLI are just two integrations I built - the engine API is designed for others to build more.
3. The SaaS adds value on top. The open-source engine handles analysis. Emailens.dev adds screenshots (real browser rendering), AI-powered fixes (Claude integration), a visual dashboard, team workflows, and hosted infrastructure. Open-sourcing the core doesn't cannibalize the paid product - it creates a funnel into it.
The Architecture of Open Source + SaaS
@emailens/engine (MIT) → Core analysis, scoring, transforms
@emailens/mcp (MIT) → Claude Desktop integration
@emailens/cli (MIT) → CI/CD and local development
emailens.dev (SaaS) → Screenshots, AI fixes, dashboard, API
Built ON TOP of @emailens/engine
The SaaS imports @emailens/engine as a dependency. Every improvement to the open-source engine automatically improves the SaaS. And every contributor to the engine is making the SaaS better too.
Getting Started
# Use the engine in your code
npm install @emailens/engine
# Use Claude for email QA
npm install -g @emailens/mcp
# Lint emails in CI/CD
npm install -g @emailens/cli
emailens lint ./emails/welcome.htmlAll three packages are MIT licensed and available on npm. The engine repository has comprehensive documentation with usage examples for every analysis type.
Questions about the open-source packages or want to contribute? Check the repos on GitHub or find me on X.