Mailers.email exposes domain health checks as both a REST API and an MCP server. This post covers the API contract, the Cloudflare Worker request path, caching, rate limiting and the design decisions behind no-key access.
The short version: POST /v1/scan accepts a domain, runs a small set of DNS checks through Cloudflare DoH, returns a structured score plus fix-first findings, and requires no account or API key. The public API is intentionally cheaper than the full web scan: blacklist checks are excluded.
Why email-domain checking works well as an API
Email configuration is mostly public DNS. MX, SPF, DKIM, DMARC, reverse DNS and domain age can all be inspected without mailbox access.
That makes the core check a good fit for a small, read-only API:
- one domain in
- a handful of DNS lookups out
- a deterministic result that another service or agent can explain
It is also safe to automate. There is no message content, no mailbox login and no sending capability.
Contract of POST /v1/scan
curl -X POST https://api.mailers.email/v1/scan \
-H "Content-Type: application/json" \
-d '{"domain":"example.com"}'
A successful response contains the domain, score, grade, confidence, individual checks and a fixes array:
{
"ok": true,
"data": {
"domain": "example.com",
"score": 69,
"maxScore": 80,
"grade": "C",
"confidence": 83,
"checks": ["mx", "spf", "dkim", "dmarc", "age", "rdns"],
"fixes": ["rdns", "mx"]
},
"meta": {
"cached": false,
"quota": {
"limit": 60,
"remaining": 59,
"scope": "hour"
}
}
}
The real response includes the record values and reasons for each check; the example is abbreviated.
About maxScore
The public API returns maxScore: 80.
This is deliberate. The score is normalized against the checks that actually ran. DNSBL checks are excluded from the public API, so a perfect public scan should not be presented as a perfect full deliverability audit.
That distinction matters when another system summarizes the result. A score should come with the scope it was measured against.
Request flow: Cloudflare Worker + DoH
The API is served by a Cloudflare Worker.
At a high level:
POST /v1/scan
-> validate domain
-> check IP rate limit
-> check KV cache by domain
-> query Cloudflare DoH for DNS records
-> build checks and score
-> cache result for 24 hours
-> return JSON
The Worker uses Cloudflare's DNS-over-HTTPS resolver for live DNS. No separate DNS service sits in the middle.
The scan currently covers:
- MX records
- SPF record and lookup count
- DKIM at common selectors
- DMARC policy
- reverse DNS / PTR
- domain age via RDAP where available
The checks are deliberately bounded. This is not a full inbox-placement or reputation test.
24-hour cache for the same domain
DNS records do not change every minute. Caching the result by domain keeps repeated checks cheap and avoids hammering upstream resolvers.
The cache is stored in Cloudflare KV with a 24-hour TTL. Cached requests still count toward the rate limit, but they skip the DNS work. The response includes meta.cached so callers know whether they received a cached result.
The cache is per domain, not per user. There is no account or API key to associate with a query.
Rate limiting: atomic counters with Durable Objects
Anonymous APIs need a limit that works under concurrency.
An ordinary read-modify-write sequence in KV is not atomic: two simultaneous requests can read the same counter and both write the same increment. For a 60/hour limit, that is a quiet way to let bursts through.
The scan API uses a Durable Object as a per-IP serialization point for counter updates. The object handles the counter operation, so increments are atomic. The API uses the same mechanism for the shared scan budget.
The public scan limit is:
- 60 requests/hour/IP
- shared by the REST scan, MCP
scan_domainand the interactive scan - no API key or account
- HTTP 429 when exceeded
The separate free DNS tool pages have their own stricter limit of 30/minute and 300/hour. The scan API and the tool pages are related products, but they are not the same quota.
Why the API excludes DNSBL
Blacklist checks are 32+ additional DNS queries per domain. That is a different cost profile from the six cheap checks above.
Keeping DNSBL out of the public API has three benefits:
- A scan stays fast and inexpensive.
- A single
scan_domainMCP call does not fan out into dozens of queries. - The API's scope stays honest: it reports DNS configuration, not live reputation.
The full blacklist scan lives in the web app at mailers.email/scan. The API documentation also states that the public scan does not include blacklist checks.
MCP: scan_domain
The same cheap scan is available as a remote MCP server:
{
"mcpServers": {
"mailers": {
"url": "https://mcp.mailers.email/mcp"
}
}
}
It exposes exactly one tool:
scan_domain
The tool is read-only. It does not access a mailbox, send email or require credentials. This keeps the permission model narrow enough for an agent to use without a long explanation.
One implementation detail: MCP calls run a fresh scan rather than using the REST API's 24-hour cache. The REST endpoint is optimized for repeated checks; the MCP path is optimized for an agent asking once during a task.
I wrote a separate MCP-focused post with client configuration and agent examples:
Giving AI agents an email deliverability check with MCP
Error handling and API behavior
The API returns JSON for normal application errors rather than an HTML error page.
Expected behavior:
-
400for an invalid or missing domain -
405for a non-POST request -
410for retired API routes; onlyPOST /v1/scanremains public -
429when the IP budget is exhausted -
502when an upstream scan cannot complete
The health endpoint is available at https://api.mailers.email/health.
Privacy and limits, stated plainly
This is a hosted service, not a self-hosted open-source scanner.
What is true:
- no account and no API key
- DNS-based checks only
- per-IP rate limits
- same-domain results cached for 24 hours
- no mailbox access and no email sending
What is not claimed:
- no inbox-placement guarantee
- no permanent "no data stored" claim
- no blacklist check in the public API
- no 100-point full audit score
Precise limits are more useful than vague promises.
What I would improve next
The current design is deliberately small. The next useful steps would be:
- Publish an OpenAPI file and client examples.
- Add a small response-schema version field.
- Add more granular error details for DNS timeouts.
- Keep the public API cheap while leaving heavier reputation checks in the web scanner.
Try it
- API and MCP docs: mailers.email/api/scan
- Full web scanner: mailers.email/scan
- GitHub README and examples: github.com/xusteve/mailers-scan-api
- Status: mailers.email/status
If you build something with the API or the MCP server, I would like to hear what worked and what was missing.
Top comments (0)