resend-email-lint-mcp
by HuluWZ
README.md
# resend-email-lint
Lint email HTML before you send it. One rule set, three interfaces:
- a **library** for your code,
- a **CLI** for CI,
- an **MCP server** so an AI agent can check an email before sending it.
Email clients are unforgiving. Missing `alt` text, relative image URLs, `javascript:` links, `<script>` tags and
messages over Gmail's ~102 KB clipping limit all fail silently. This tool catches them with clear messages and line numbers.
It works with any sender. It does not call any email API.
## CLI
```sh
npx resend-email-lint ./emails
```
```
examples/bad.html
1:1 warning Missing <!DOCTYPE html>. (missing-doctype)
3:3 error Image has no alt attribute. Add alt="" if it is decorative. (img-alt)
3:3 error Image src "/logo.png" is relative. Use an absolute https:// URL or a cid: reference. (img-src-relative)
5:3 error Link has an empty href. (link-href)
6:3 error <script> is not supported in email and will be removed. (no-script)
4 errors, 4 warnings
```
Exit codes: `0` clean, `1` lint errors (or too many warnings), `2` usage or I/O error.
| Option | Meaning |
| ------------------------- | ------------------------------------------- |
| `-f, --format text\|json` | Output format |
| `--marketing` | Also run bulk-mail rules (unsubscribe link) |
| `-i, --ignore <rule>` | Skip a rule (repeatable) |
| `--max-warnings <n>` | Fail if warnings exceed `n` |
| `--list-rules` | Print every rule |
Read from stdin with `-`: `cat email.html | npx resend-email-lint -`
**React Email:** lint the rendered HTML, not the `.tsx`. Render first (for example with `email export`, or `render()` from
`@react-email/render`), then point the CLI at the output.
### In CI
```yaml
- run: npx resend-email-lint ./out/emails --max-warnings 0
```
## Library
```ts
import { lintHtml } from "resend-email-lint";
const { diagnostics, errorCount } = lintHtml(html, { marketing: false, ignore: ["html-lang"] });
if (errorCount > 0) throw new Error(diagnostics.map((d) => d.message).join("\n"));
```
Example with Resend, checking before you send:
```ts
import { Resend } from "resend";
import { lintHtml } from "resend-email-lint";
const resend = new Resend(process.env.RESEND_API_KEY);
const { errorCount, diagnostics } = lintHtml(html);
if (errorCount > 0) throw new Error(JSON.stringify(diagnostics));
await resend.emails.send({ from, to, subject, html });
```
`lintHtml` is pure and synchronous: no I/O, no network.
## MCP server
Add to your MCP client config (Claude Desktop, Cursor, and so on):
```json
{
"mcpServers": {
"email-lint": {
"command": "npx",
"args": ["-y", "-p", "resend-email-lint", "resend-email-lint-mcp"]
}
}
}
```
Tools:
- `lint_email_html`: takes `html` (and optional `marketing`, `ignore`) and returns diagnostics with rule id, severity, line and column.
- `list_rules`: lists every rule.
**Designed for agents as users.** Tool descriptions say when to call them ("before sending"). Errors name the valid rule ids so an
agent can self-correct. Diagnostics are structured JSON an agent can act on. The server is also deliberately **pure**: it has no
filesystem, network or environment access, and caps input at 2 MB, so handing it to an agent adds no new permissions.
## Rules
| Rule | Severity | Checks |
| --------------------- | -------- | -------------------------------------------------------- |
| `img-alt` | error | `<img>` has an `alt` attribute |
| `img-src-relative` | error | Image URL is absolute (or `cid:` / `data:`) |
| `link-href` | error | Links have a real href (not empty, `#` or `javascript:`) |
| `no-script` | error | No `<script>` |
| `link-https` | warning | Links use https |
| `no-external-css` | warning | No external stylesheets |
| `html-size` | warning | Under Gmail's 102 KB clipping limit |
| `missing-doctype` | warning | Documents declare a doctype |
| `html-lang` | warning | `<html lang>` is set |
| `missing-title` | warning | `<title>` is present |
| `missing-unsubscribe` | warning | Unsubscribe link present (`--marketing` only) |
Document-level rules only run when the input contains an `<html>` tag, so fragments are not flagged.
## Contributing
New rules are the easiest way in. See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues