Open-Sourcing Emailens: Engine, MCP Server, and CLI for Email QA

March 4, 2026 (4mo ago)

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 tags

CSS 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/mcp

Add 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 --screenshots

audit - Comprehensive audit (CSS + spam + links + accessibility + images):

emailens audit ./emails/welcome.mjml --json

fix - 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 fixes

export - Generate self-contained HTML or JSON reports:

emailens export ./emails/welcome.html --format html --output report.html

Multi-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)
  • .html in 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: error

Why 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.html

All 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.