
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
@email-lint/core
Advanced tools
Lint email HTML for client compatibility using caniemail data. CLI + library.
Lint email HTML for client compatibility. Validates CSS properties, HTML elements, and attributes against caniemail data for 30+ email clients.
$ email-lint check welcome.html
welcome.html
12:5 error cursor not supported (4/4 variants) [gmail]
18:3 warning background-image not supported (2/6 variants: windows, windows-mail) [outlook]
24:10 warning <picture> not supported (4/4 variants) [gmail]
✖ 1 error, 2 warnings
You can, and often you should. caniemail parses your HTML and CSS, returns issues split into errors and warnings, and ships groupIssues() and formatIssue() to help you present them. It is a good library, and email-lint is built on it.
What it does not do is decide what matters in your codebase, or put an answer in front of you in CI. That is what a linter is for:
cursor not supported (4/4 variants) [gmail]cursor is cosmetic in email (downgraded to info). Gmail forces target="_blank" on all links, so if you already use it, that's info not errortarget="_blank" to its output. caniemail flags all of these. email-lint knows they're framework artifacts and suppresses thememail-lint check exits 1 on errors and 0 when the only findings are warnings, so CI blocks on the things that break and stays quiet about the rest.tsx component and it renders + lints in one step, no build pipeline needednpm install @email-lint/core
# Lint HTML files
email-lint check emails/*.html
# Lint React Email components directly (.tsx/.jsx)
email-lint check src/emails/welcome.tsx
# Target specific clients
email-lint check welcome.html --preset gmail
email-lint check welcome.html --preset outlook
# Several at once, for the union of their clients
email-lint check welcome.html --preset gmail,outlook
# Drop clients your audience never uses
email-lint check welcome.html --exclude orange,mail-ru
email-lint check welcome.html --preset all-clients --exclude '*.android'
# Output formats
email-lint check welcome.html --format pretty # default, colored terminal output
email-lint check welcome.html --format json # structured JSON for tooling
email-lint check welcome.html --format github # GitHub Actions annotations
# Expand collapsed diagnostics to one line per variant
email-lint check welcome.html --verbose
Exits with code 1 when errors are found — drop it into CI and it just works.
| Preset | Clients |
|---|---|
all-clients | All 30+ clients (default) |
gmail | Gmail desktop, iOS, Android, mobile web |
outlook | Outlook desktop, web, Windows Mail |
apple-mail | Apple Mail macOS, iOS |
yahoo | Yahoo Mail desktop, iOS, Android |
When you pass .tsx or .jsx files, the CLI automatically:
@react-email/componentsExport your component as default and optionally export testData for props:
export const testData = { name: 'Jane' };
export default function Welcome({ name }: { name: string }) {
return (
<Html>
<Body>
<Text>Hey {name}</Text>
</Body>
</Html>
);
}
email-lint check src/emails/welcome.tsx
Use --no-ignore-framework to disable framework filtering, or --show-ignored to see suppressed diagnostics in a separate section.
import { lint } from '@email-lint/core';
const html = '<div style="cursor: pointer;">Click</div>';
const result = lint(html, { preset: 'gmail' });
console.log(result.success); // false
console.log(result.errorCount); // 1
console.log(result.diagnostics); // LintDiagnostic[]
lint(html, config?)Returns a LintResult with diagnostics grouped by client family.
interface LintResult {
diagnostics: LintDiagnostic[];
errorCount: number;
warningCount: number;
success: boolean;
}
interface LintDiagnostic {
severity: 'error' | 'warning' | 'info';
title: string; // Feature name from caniemail
message: string; // Human-readable message with notes
family: string; // "gmail", "outlook", etc.
variants: string[]; // Affected variants within the family
familySize: number; // Total variants in this family
position?: {
start: { line: number; column: number };
end: { line: number; column: number };
};
}
interface LintConfig {
preset?: string; // Client preset (default: "all-clients")
exclude?: string; // Clients to drop, comma-separated globs
framework?: 'react-email'; // Enable framework-aware filtering
showIgnored?: boolean; // Keep suppressed diagnostics in output
}
import { lint, formatPretty, formatJson, formatGitHub } from '@email-lint/core';
const result = lint(html);
console.log(formatPretty([result]));
console.log(formatJson([result]));
console.log(formatGitHub([result]));
cursor is cosmetic in email, so it downgrades to info. Gmail forces target="_blank" on all links, so if you already use it, that's info too(4/4 variants)target="_blank")- name: Lint emails
run: email-lint check 'src/emails/**/*.tsx' --format github
The github formatter emits workflow commands that show inline annotations on your PR diffs.
MIT
FAQs
Lint email HTML for client compatibility using caniemail data. CLI + library.
The npm package @email-lint/core receives a total of 18,812 weekly downloads. As such, @email-lint/core popularity was classified as popular.
We found that @email-lint/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.