EMAIL GUARD

Check the mail before your agent acts on it.

Once an agent reads and sends email, the inbox becomes an attack surface. An incoming message can hijack the agent with instructions hidden in the body, spoof a sender it trusts, or bait it into a phishing link. On the way out, an agent can leak a secret or torch its own sending domain. Email Guard is the deterministic check for both directions: no LLM, so it can't be talked out of its verdict by the email it's scanning.

FreeDeterministic, no LLMReads SPF / DKIM / DMARC resultsRDAPHTTP + MCP

Try it

verdict appears here…

The endpoints

EndpointWhat it does
POST /api/scan-inboundThe guard. Before the agent acts on a message: known injection and hijack patterns (including zero-width, bidi, and hidden-HTML payloads), sender header checks (SPF/DKIM/DMARC results as reported by the receiving server, reply-to mismatch, a 24-name brand list against the display name), a DNS and RDAP look at the sender domain (disposable, or registered in the last 30 days), link reputation → verdict allow / review / block plus fixed-shape metadata
POST /api/scan-outboundBefore the agent sends: 22 secret patterns and 3 PII patterns over the subject, body and HTML (with a redacted copy), deliverability problems that burn the sender domain, dead recipients (disposable, or no MX = guaranteed bounce). Credential formats outside those 22 patterns are not detected, and the email-address rule is noisy: any address in the body puts the message at review
GET /api/check-domain-authSPF, DMARC, MX, domain age (RDAP), and disposable status for any domain or address → weak / enforced

What the sender check compares

Four string comparisons on headers. Reply-To domain against From domain and Return-Path domain against From domain, by exact equality, so a legitimate mail.example.com sending for example.com trips it too. An address written inside the display name that differs from the real From address. And a 24-name brand list (paypal, apple, microsoft, google, amazon, netflix, coinbase, chase, irs, docusign, usps, fedex and the like) matched as a substring of the display name, flagged only when that same string is missing from the From domain.

That last one is the brand-impersonation flag, and it is narrow. "PayPal Security" <svc@paypal-secure.tk> is not flagged, because "paypal" is in the domain. "Barclays Bank" <svc@evil.tk> is not flagged, because Barclays is not on the list. "PayPaI Security" with a capital i is not flagged, because the substring stops matching. There is no general impersonation detection here.

What the authentication check does and does not do

I read SPF, DKIM and DMARC results out of the Authentication-Results header that the receiving mail server wrote. Nothing here verifies a DKIM signature. No cryptography is involved at any point.

DKIM DNS records are never looked up either. The record lives at <selector>._domainkey.<domain> and the selector cannot be derived from a domain name alone, so check-domain-auth reports SPF and DMARC only and returns an explicit dkim: { checked: false }.

This matters for how much you trust the result. For mail your own server received, that header is a good signal. For a raw .eml handed to you by an untrusted party it is worthless, because an attacker can type dkim=pass into the file. Every response says which situation applies and carries verified_here: false, so an agent cannot mistake a value we read for one we checked.

Use it from an agent (MCP)

{ "mcpServers": { "email-guard": { "command": "npx", "args": ["-y", "email-guard-mcp"] } } }

Or call the HTTP endpoints directly — JSON in, JSON out, CORS open. OpenAPI spec.

What the structured output is, and what it isn't

The dangerous move is letting an agent reason over the raw body of a hostile email, because that is where the injection lives. scan-inbound gives you a fixed JSON shape instead: verdict, risk, score, sender fields, auth results, domain age, injection findings, link analysis. The agent can decide what to do from that without re-reading the attack.

The shape is fixed. Nothing in it is sanitised. Every field holding free text holds the attacker's text, copied verbatim. subject is the first 200 characters of the subject line. sender.display, sender.from, sender.replyTo and sender.domain come straight off the headers. links[].url and links[].host are the URL as written. injection.findings[].match is up to 120 characters of the matched body text. The note strings in sender.spoofFlags, and the reasons list, quote those same addresses back at you. The verdict, the scores, the rule ids and the advice string are mine; anything with free text in it came out of the email. Wrapping it in JSON does not make it safe to obey. Treat it as data, the same as the body.

Data sources are all free and public: DNS, RDAP, disposable-domain lists, and the injection and secret rulesets. Both scan endpoints return the ruleset version as rules_version.