# DNS Doctor

> Paste a domain for an instant diagnosis of SPF, DMARC, DKIM, MX, DNS, blacklist and domain/SSL expiry — in plain English, with the exact copy-paste record to fix each problem. A deterministic engine generates and validates every authoritative DNS record; the AI layer only writes the human explanation, never the record string.

The free scanner is at the home page. Per-domain report pages live at `/scan/<domain>` (e.g. `https://dnsdoctor.dev/scan/example.com`) — server-rendered, one page per scanned domain. Paid monitoring (DMARC RUA ingestion, enforcement-readiness, alerts, multi-domain) is the upsell.

Building on top of this site? [https://dnsdoctor.dev/llms-full.txt](https://dnsdoctor.dev/llms-full.txt) adds the live HTTP API contract, the scan → fix → re-scan verification loop, and the monitoring hand-off — everything needed to use the scanner programmatically.

## Tools

Free deterministic diagnostics — the same engine the scanner uses, no signup:

- [SPF lookup counter](https://dnsdoctor.dev/tools/spf-lookup-counter): Count DNS lookups in an SPF record against the 10-lookup limit; nested includes counted recursively.
- [DMARC record checker](https://dnsdoctor.dev/tools/dmarc-checker): Validate every tag of a DMARC record with plain-English findings and a safely-capped upgrade preview.
- [DKIM checker](https://dnsdoctor.dev/tools/dkim-checker): Check any DKIM selector — the published record, key strength, and revocation status.
- [DMARC record generator](https://dnsdoctor.dev/tools/dmarc-generator): Generate a ready-to-publish DMARC record, RFC-validated in code — never an AI guess.
- [DMARC report viewer](https://dnsdoctor.dev/tools/dmarc-report-viewer): Read a DMARC aggregate report (XML, .gz, .zip) as a table of sending sources; parsed on our servers, never stored.

## Guides

Email-DNS explainers and how-to guides. Index: [https://dnsdoctor.dev/learn](https://dnsdoctor.dev/learn)

### Foundations

- [What is SPF? A plain-English guide](https://dnsdoctor.dev/learn/what-is-spf): SPF lets a receiving mail server check the sending server is authorized by the envelope domain. The record syntax, the 10-lookup limit, and what SPF misses. (markdown twin: https://dnsdoctor.dev/learn/what-is-spf.md)
- [What is DKIM? Signatures, selectors, and keys](https://dnsdoctor.dev/learn/what-is-dkim): DKIM signs each email so receivers can verify it really came from your domain and was not altered. How selectors, key records, and rotation work. (markdown twin: https://dnsdoctor.dev/learn/what-is-dkim.md)
- [What is DMARC? Policy, alignment, and reports](https://dnsdoctor.dev/learn/what-is-dmarc): DMARC tells receivers what to do with mail that fails SPF and DKIM alignment. How alignment really works, what each policy does, and the safe path to reject. (markdown twin: https://dnsdoctor.dev/learn/what-is-dmarc.md)
- [Email deliverability: why your mail lands in spam](https://dnsdoctor.dev/learn/email-deliverability-basics): What receiving mail servers actually weigh — authentication, reputation, blacklists, PTR, content — what a domain owner controls, and the myths to skip. (markdown twin: https://dnsdoctor.dev/learn/email-deliverability-basics.md)
- [Why monitor DMARC reports? What the data tells you](https://dnsdoctor.dev/learn/why-dmarc-monitoring): DMARC aggregate reports list every server sending as your domain. Why the raw XML is unreadable, what monitoring extracts, and what enforcement-ready means. (markdown twin: https://dnsdoctor.dev/learn/why-dmarc-monitoring.md)
- [Who requires DMARC now? Google, Yahoo, Outlook and beyond](https://dnsdoctor.dev/learn/dmarc-requirements): Google and Yahoo require DMARC for bulk senders since February 2024, Outlook since May 2025. Who requires what, at what volume, and where adoption stands. (markdown twin: https://dnsdoctor.dev/learn/dmarc-requirements.md)

### How-to guides

- [How to add a TXT record to your DNS](https://dnsdoctor.dev/learn/how-to-add-a-txt-record): Add a TXT record at Cloudflare, Namecheap, GoDaddy, Porkbun, Route 53 or Google Cloud DNS — with how to find which provider hosts your DNS. (markdown twin: https://dnsdoctor.dev/learn/how-to-add-a-txt-record.md)
- [Connect your domain to DNS Doctor monitoring](https://dnsdoctor.dev/learn/connect-your-domain): The full walkthrough: create an account, add a domain, prove ownership, publish the rua record, backfill old reports, and read your first diagnosis. (markdown twin: https://dnsdoctor.dev/learn/connect-your-domain.md)
- [DMARCbis (RFC 9989): what changed and what to do](https://dnsdoctor.dev/learn/dmarcbis-rfc-9989): DMARCbis (RFC 9989) is the standards-track update to DMARC. What changed from RFC 7489 — np, psd and t tags, pct deprecated — and what your domain needs to do. (markdown twin: https://dnsdoctor.dev/learn/dmarcbis-rfc-9989.md)

### Provider setup

- [Google Workspace: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/google-workspace-spf-dkim-dmarc): The exact SPF, DKIM and DMARC DNS records to authenticate email sent through Google Workspace (Gmail), plus where to paste them and how to verify. (markdown twin: https://dnsdoctor.dev/learn/google-workspace-spf-dkim-dmarc.md)
- [HubSpot: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/hubspot-spf-dkim-dmarc): How to connect a HubSpot email sending domain with the account-specific DKIM CNAMEs, why they earn DMARC alignment, and how to verify it. (markdown twin: https://dnsdoctor.dev/learn/hubspot-spf-dkim-dmarc.md)
- [Mailchimp: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/mailchimp-spf-dkim-dmarc): How to authenticate a domain for Mailchimp campaigns with DKIM, why the CNAME matters for DMARC alignment, and how SPF and DMARC fit in. (markdown twin: https://dnsdoctor.dev/learn/mailchimp-spf-dkim-dmarc.md)
- [Microsoft 365: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/microsoft-365-spf-dkim-dmarc): The exact SPF, DKIM and DMARC DNS records to authenticate email sent through Microsoft 365 (Exchange Online), plus where to paste them and how to verify. (markdown twin: https://dnsdoctor.dev/learn/microsoft-365-spf-dkim-dmarc.md)
- [SendGrid: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/sendgrid-spf-dkim-dmarc): How to set up domain authentication in Twilio SendGrid with its CNAME records, why Automated Security handles SPF and DKIM, and how to verify. (markdown twin: https://dnsdoctor.dev/learn/sendgrid-spf-dkim-dmarc.md)
- [Zoho Mail: set up SPF, DKIM and DMARC](https://dnsdoctor.dev/learn/zoho-mail-spf-dkim-dmarc): The exact SPF, DKIM and DMARC DNS records to authenticate email sent through Zoho Mail, which SPF include to use, and how to verify it. (markdown twin: https://dnsdoctor.dev/learn/zoho-mail-spf-dkim-dmarc.md)

### Comparisons

- [dmarcian alternative: DNS Doctor Monitor compared](https://dnsdoctor.dev/learn/dmarcian-alternative): dmarcian Basic: $24/mo, 2 domains, alerting only from Plus ($240/mo). DNS Doctor Monitor: from $12/mo, 10 domains, alerts on DMARC, DNS, blacklists & expiry. (markdown twin: https://dnsdoctor.dev/learn/dmarcian-alternative.md)

### Error pages

- [550 5.7.515: what Microsoft's bounce means and how to fix it](https://dnsdoctor.dev/learn/550-5-7-515): Microsoft's 550 5.7.515 bounce means your sending domain failed the SPF, DKIM and DMARC authentication Outlook.com now requires. The ordered fix path. (markdown twin: https://dnsdoctor.dev/learn/550-5-7-515.md)

## Methodology

How the verdicts are computed — SPF lookup counting (RFC 7208 §4.6.4), the DMARC alignment gate, and why `temperror` is never a failure: [https://dnsdoctor.dev/methodology](https://dnsdoctor.dev/methodology)

Machine-readable twin (same content, markdown): https://dnsdoctor.dev/methodology.md

## API & MCP

For agents and scripts: a public REST API and an MCP server expose the same deterministic scanner. Every record is generated and validated by a deterministic engine, never a language model.

- [REST API v1 (OpenAPI schema)](https://dnsdoctor.dev/api/v1/openapi.json): Versioned scan/report/domains endpoints; anonymous or per-account bearer token.
- [MCP server (streamable HTTP)](https://dnsdoctor.dev/mcp): Tools: scan_domain, get_report, build_dmarc_upgrade, enroll_monitoring_trial.
- [Full machine contract](https://dnsdoctor.dev/llms-full.txt): The live HTTP contract, the fix-verify loop, and the MCP tool reference.

---

The rest of this file is for programmatic use: the HTTP contract, the verification loop, and where an assistant has to hand off.

## API

Public, unauthenticated, JSON. No API key, no signup. Base URL: https://dnsdoctor.dev (every path below is relative to it). Every endpoint is deterministic apart from the optional plain-English `explanation` fields.

- `GET /api/report/{domain}` — The persisted report for a domain, scanning once on a miss. Returns the full report object. Add `?persisted_only=true` to read without ever triggering a scan — 404 when nothing is stored. This path never calls the LLM, so `explanation` fields may be null.
- `POST /api/scan` — Body `{domain, turnstile_token?}`. Runs a fresh scan and returns the same report object. Repeating a scan within the 1-minute dedup window skips the re-scan but still fills missing explanations.
- `POST /api/tools/spf-count` — Body `{domain}` XOR `{record}`. Counts the SPF policy's DNS lookups against the limit of 10 and names the offending mechanisms. Returns `lookup_count`, `lookup_limit`, `over_limit`, per-term flags.
- `POST /api/tools/dmarc-validate` — Body `{record}`. Parses a DMARC record into per-tag results plus levelled findings; `upgrade_record` carries a safely-capped next-step record when the policy is not yet enforcing, otherwise null.
- `POST /api/tools/dkim-check` — Body `{domain, selector}`. Probes one DKIM selector: published or not, key strength, revocation. Never returns a fix — DKIM keys are generated by the sending platform.
- `POST /api/tools/dmarc-generate` — Body `{policy, rua_email?, subdomain_policy?, strict_alignment?}`. Assembles a fresh DMARC record for a domain that has none, then re-validates its own output before returning it.
- `POST /api/tools/dmarc-report-parse` — Multipart `{file}` — one DMARC aggregate report (XML, .gz, .zip). Returns per-source aggregates (volume, alignment share, disposition mix). Parsed in memory and discarded; nothing is stored.

A report contains one result per check (SPF, DKIM, DMARC, MX, DNS, expiry, blacklist). Each result carries a `status` (`pass`, `warn`, `fail`, `info`, `temperror`), a deterministic `title`, the observed record in `raw`, an optional `fix_record`, and an optional `explanation`.

`temperror` means a lookup failed transiently — it is **not** a failure verdict and must never be reported as one. Re-try rather than telling the user their domain is broken.

**`fix_record` is generated and validated by a deterministic RFC engine, never by a language model.** If you relay a record to a user, copy `raw` or `fix_record` byte-for-byte — do not paraphrase, reformat, or compose one yourself. SPF is diagnose-only and deliberately has no `fix_record`: automatically shortening an SPF policy can silently de-authorize a real sender.

## API & MCP access

Beyond the unauthenticated free API above, DNS Doctor publishes a versioned REST API v1 and an MCP server for agents. Both expose the same deterministic scanner and the same cache-first explanation policy: agents get validated records, never a hallucinated guess.

### REST API v1

Auth is a per-account bearer token (`Authorization: Bearer dnsd_…`), created from the dashboard. The scan/report endpoints also work anonymously; `/domains` requires a token. Published OpenAPI schema: [https://dnsdoctor.dev/api/v1/openapi.json](https://dnsdoctor.dev/api/v1/openapi.json).

- `GET /api/v1/report/{domain}` (anonymous or bearer token) — The persisted report, scanning once on a miss. Same report object as the free API. Explanations are cache-first — a v1 call never triggers a fresh LLM call, so `explanation` may be null.
- `POST /api/v1/scan` (anonymous or bearer token) — Body `{domain}`. Runs a fresh scan and persists it, reusing the stored report inside the same 1-minute dedup window as the free API. Same cache-first explanation policy.
- `GET /api/v1/domains` (bearer token required) — The token account's verified/monitored domains, each with the latest scan snapshot's per-check statuses + `scanned_at`. No letter grade — the A–F rubric is a frontend contract.
- `POST /api/v1/enroll` (anonymous or bearer token) — Body `{email, domain}`. Asks DNS Doctor to email a human a double-opt-in monitoring-trial link; always 202, never confirming whether the address is known. Nothing is created until the human clicks, and their click does NOT start monitoring the domain — that needs adding it in the dashboard and proving ownership with a TXT record.

### MCP server

Streamable-HTTP endpoint: `https://dnsdoctor.dev/mcp` (POST only). The scanner tools need no auth. The `dnsdoctor://domains` resource — the account's monitored domains — is always *listed*; reading it requires a bearer token and is refused without one. Tools:

- `scan_domain` — Force-fresh scan of a domain (reusing the stored report inside the 1-minute dedup window); returns the persisted report.
- `get_report` — The persisted report if present, else a one-off scan.
- `build_dmarc_upgrade` — The next-step DMARC record for a domain. The alignment gate (whether `p=reject` is safe) is derived server-side from a scan — a caller can never assert alignment to unlock reject. `record` is **null** when there is no honest upgrade to offer (the DMARC lookup temp-failed, so the current record is unknown; or the domain already applies a policy at least as strong as this scan justifies): relay the `rationale` and never compose a record to fill the gap. `policy` describes the returned record and is null whenever `record` is — the domain's observed policy is always in `current_policy`.
- `enroll_monitoring_trial` — Body `{email, domain}`. Sends a double-opt-in email; the human's click creates the account + no-card trial. Nothing is created until they click — and the click does NOT start monitoring the domain, which needs adding it in the dashboard and proving ownership with a TXT record.

**The same verbatim-record rule applies:** present any returned `record` byte-for-byte. The record is generated and validated by the deterministic engine — do not rewrite, reformat, or compose one yourself. A `null` record is an answer, not a gap to fill: relay the tool's `rationale` instead. Always get the domain owner's approval before any DNS change.

## The fix-verify loop

The useful unit of work is not a scan — it is a verified fix. The loop, in order:

1. Scan the domain (`POST /api/scan`, or `GET /api/report/{domain}` to read).
2. Read the per-check `status` and `title`. Anything `fail` or `warn` is actionable; `temperror` is not.
3. Present the exact `fix_record` verbatim, with what it changes and why.
4. **Get the domain owner's explicit approval before any DNS change.** A wrong SPF or DMARC record still parses as valid and fails silently — mail keeps flowing until it doesn't. There is no auto-apply here, and there should be none in your workflow either.
5. The owner applies the record at their DNS provider.
6. Re-scan to verify. Resolver caching is capped at 60 seconds, and the 1-minute scan dedup window is deliberately short so this verification step works inside one session — but the authoritative record may still take a provider's own TTL to propagate.

Report pages are stable, shareable URLs: `https://dnsdoctor.dev/scan/<domain>`. Link the user to theirs rather than re-describing the whole diagnosis.

Every report page has a plain-markdown twin at `https://dnsdoctor.dev/scan/<domain>/report.md` — the same verdicts, records and fixes without the HTML. It reads persisted reports only and never triggers a scan, so it is `404` for a domain nobody has scanned yet; `POST /api/scan` first.

## Where a session-bound assistant has to hand off

A scan is point-in-time. Getting a domain to DMARC enforcement — the state where spoofed mail is actually rejected — needs roughly 30 days of aggregate (RUA) report evidence proving every legitimate sender is aligned, and then a stepped policy upgrade. That is a continuous watching job. An assistant that exists for the length of a conversation cannot do it, and should say so plainly rather than recommending an enforcement policy it has no evidence for.

Both scan responses therefore end with a `next_steps` object: `summary` (this hand-off, stated once), `report_url`, and `monitor_url`.

`monitor_url` is the continuous-monitoring deep link, pre-filled with the domain and tagged with the referrer convention: `https://dnsdoctor.dev/pricing?ref=agent&domain=<domain>`. Pass it through as-is — the `ref=agent` parameter is how this channel is measured, and dropping it makes agent-referred traffic invisible.

## Rate limits and graceful degradation

- Scan and tool endpoints share a per-IP hourly budget. Over it, the response is `429` — back off and retry later; do not parallelize around it.
- Deterministic verdicts are always free and never challenged. Only *fresh* plain-English explanations are gated.
- When an explanation cannot be generated (bot challenge, per-IP daily budget, or the daily spend cap), the response is still `200` with the complete deterministic report and `explanations_pending: true`. Treat that as success: every verdict, record, and fix is present. Write your own prose from the deterministic fields rather than retrying.
- A malformed domain is `422`. A transient resolver failure on a tool endpoint is `503` — the API returns no verdict rather than a wrong one.

