← Plugin catalog
Developer Tools

DNS Doctor

Andriy Shendrikov v1.9.0

Publisher description

From the marketplace listing

DNS Doctor (https://dnsdoctor.dev) — DMARC monitoring plus SPF, DKIM and DMARC diagnostics for any domain. Scan email authentication, find SPF lookup-limit problems, check DKIM and DMARC, generate validated DMARC records, monitor DMARC RUA reports and new senders, and move from p=none to quarantine on a scan's evidence and to reject on report evidence. Free domain scans and a free monitoring trial. Every DNS change is human-approved. Fix records are generated deterministically and RFC-validated, never guessed by AI; SPF is diagnosed, never rewritten. Twenty tools: scan a domain (SPF, DKIM, DMARC, MX, DNS health, blacklists, domain and TLS expiry), read the stored report, look up a domain's registration (registrar, expiry, nameservers, locks), build a validated DMARC upgrade or a DMARC record from scratch, validate a pasted DMARC record, count SPF lookups, audit SPF includes, check a DKIM selector, check a record on the domain's own nameservers, check global propagation from six vantage points, check reverse DNS, parse a DMARC aggregate report, build the hardening pack for a non-sending domain, hand the owner a monitoring sign-up link, and for signed-in operators read the monitoring alert log and the enforcement-readiness verdict. Three account tools add a domain to monitoring, run the ownership check and read the records to publish; today they hand the owner the same sign-up link. The assistant never signs the user in, never emails on their behalf, and never applies a DNS change: it explains the findings and presents each record for the human to publish.

Language: English · Automatically detected from descriptions.

Files & skills

File archives

Plugin package3 files · 14.4 KBBrowse files →
Skill instructions
dns-doctor34.8 KB

View saved version →

---
name: dns-doctor
description: Use for any question about a domain's DNS or email deliverability. Triggers include looking up a record (A, AAAA, CNAME, MX, TXT, SPF, DMARC) or a DKIM selector, checking whether a DNS change has propagated worldwide, the reverse DNS of a mail-server IP, a domain or certificate about to expire, hardening a parked domain, and every email-authentication symptom such as mail landing in spam, SPF PermError / "too many DNS lookups", DMARC stuck at p=none, a "550 5.7.515" or "550 5.7.1" rejection, DKIM failures, or a possible blacklist listing. Scans, fixes and verifies the domain against the DNS Doctor MCP server and returns copy-paste fix records generated by a validating engine, never a guessed record.
license: Apache-2.0
compatibility: Talks to the DNS Doctor MCP server at https://dnsdoctor.dev/mcp (hosted) or via the local `@dnsdoctor/mcp` stdio client; needs outbound HTTPS.
metadata:
  {
    "homepage": "https://dnsdoctor.dev/methodology",
    "author": "DNS Doctor"
  }
---

# DNS Doctor — the DNS, DMARC, SPF and DKIM skill

DNS Doctor scans, fixes and verifies a domain's DNS — email authentication (SPF,
DMARC, DKIM) first, plus multi-region propagation, SPF include supply-chain
audits, MX, DNS health, blacklists and domain/SSL expiry — and returns
**deterministic verdicts** plus **copy-paste fix records generated by a
validating engine**. The
record you get back is verified against RFC grammar and the SPF 10-lookup limit —
it is never an LLM guess. Your job is to run the scan, explain the findings, hand
the human the exact record, and confirm the fix — not to author DNS records
yourself.

## What this skill sends, and where

Every tool call goes to one host, `https://dnsdoctor.dev` (or the local `@dnsdoctor/mcp`
client, which calls the same public API). What leaves the machine is exactly what the user
asked to check: a domain name, and for the focused tools a record name, an IP address, a DKIM
selector, a pasted DMARC record, or an uploaded DMARC report. Nothing else is read or sent — no
files, no environment beyond an optional `DNSDOCTOR_API_TOKEN`, no message contents.

Two things the user should know before you scan a domain for them:

- **A scan result is a public report page** at `https://dnsdoctor.dev/scan/<domain>` (the free
  scanner is a public service, like a DNS lookup site). Do not scan a domain the user wants kept
  private, and say so if they ask.
- **The optional API token** (`DNSDOCTOR_API_TOKEN`) goes to dnsdoctor.dev only, as an
  `Authorization` header, and only if the user put it in your environment; the local
  `@dnsdoctor/mcp` client attaches it to its requests, and the server uses it for the two
  monitoring reads (`get_alerts`, `get_readiness`) and the `dnsdoctor://domains` resource. Never
  send any other credential, and never ask for one.

DNS Doctor never changes DNS: it returns records for a human to publish.

## When to use this skill

Reach for it whenever a user describes any of:

- "our email is going to spam" / "customers aren't getting our mail"
- "SPF PermError" or "too many DNS lookups" (the SPF 10-lookup limit)
- "DMARC is stuck at p=none" / "how do I get to p=reject safely"
- a bounce code like `550 5.7.515`, `550 5.7.1`, or `dmarc=fail`
- "is my domain blacklisted?" / "am I on a blocklist?"
- "when does my domain / TLS certificate expire?"
- "who can send as us?" / auditing SPF includes before or after an email
  provider migration
- locking down parked, redirect or brand-defensive domains that send no mail

## Connect to the server

The tools come from the DNS Doctor MCP server. Two ways to reach it:

- **Streamable HTTP (default, no setup):** `https://dnsdoctor.dev/mcp` — anonymous
  access exposes all the scanner tools. This plugin's `.mcp.json` already points
  here.
- **Local stdio:** `npx -y @dnsdoctor/mcp` — the same 16 tools, run as a local
  process that calls the public DNS Doctor REST API. Use it when your client
  prefers stdio, or when you want the server under your own supervision. Set
  `DNSDOCTOR_API_TOKEN` in its environment to use a token (optional for the
  diagnosis tools; required for the two monitoring reads).

The fourteen diagnosis tools need no token and are enough for a one-off
diagnosis. An API token (`Authorization: Bearer dnsd_…`) additionally unlocks:

- `get_alerts` and `get_readiness` — the two monitoring reads, which return one
  account's own data. They are **listed for everyone and callable with a token**:
  they appear in the tool list whether or not you have one, and without a valid
  token the call is refused with guidance rather than hidden.
- the `dnsdoctor://domains` resource — the account's continuously-monitored
  domains. Over HTTP that resource is likewise always *listed* and refused
  without a token.

**You cannot create a token, so do not try.** Minting is session-authenticated:
the account owner creates one while signed in, at `/dashboard/settings` on the
DNS Doctor site. A refusal tells you the exact page — relay that link to the
person you are helping and let them decide whether they want one. **Never ask
anyone to paste a token, or any other credential, to you.**

## Tools

The four whole-domain tools — start here for any "is this domain's email set up
correctly" question:

| Tool | Input | Returns |
|---|---|---|
| `scan_domain` | `{ domain }` | Forces a fresh scan; the full report. Re-scanning the same domain within a minute reuses the stored report. |
| `get_report` | `{ domain }` | The persisted report (scans once if none exists). |
| `build_dmarc_upgrade` | `{ domain }` | A validated DMARC enforcement record + rationale. Scans fresh — the record edits the domain's *current* tags, so it is never built on a stale one. |
| `start_monitoring_signup` | `{ domain }` | A sign-up link to hand to the human who owns the domain, plus a `message` to relay. **Sends no email and creates nothing** — the human opens the link, signs in on our page themselves (a social provider or an emailed link, whichever that deployment offers), and the domain is carried over to their dashboard, already filled in, from there. |
| `add_monitored_domain` | `{ domain }` | **Needs a linked account** (see below). Adds the domain to the user's monitoring and returns the ownership TXT record to publish, where their DNS is hosted, a provider-specific guide link and — where the provider serves our template — a one-click apply URL. Re-adding an already-monitored domain returns it, not an error. |
| `check_domain_verification` | `{ domain }` | **Needs a linked account.** Re-checks the ownership record and marks it verified on a match. Says WHICH outcome (`not_found` / `mismatch` / `transient` / `verified`) and which nameservers were asked — a `transient` outcome is OUR lookup, never a verdict about their DNS. On success it also carries the DMARC reporting record, which REPLACES their existing DMARC TXT rather than sitting beside it. |
| `get_domain_records` | `{ domain }` | **Needs a linked account.** Read-only: the ownership record while unverified, and once verified the DMARC reporting record plus whether we have observed it published. |

Ten focused tools for the single questions a full scan over-answers. Each runs
the same validating engine:

| Tool | Input | Returns |
|---|---|---|
| `count_spf_lookups` | exactly one of `{ domain }` or `{ record }` | **The SPF validator — there is no separate one.** `record_valid`, per-term `findings`, `has_pass_all` (a `+all`, which authorizes the whole internet to send as the domain), `multiple_all` (everything after the first `all` is unreachable), the parsed `terms`, and the DNS lookups the record costs against the RFC 7208 limit of 10 with the offending mechanisms named. `domain` resolves the published record and counts recursively through nested includes; `record` parses a pasted record, its own terms only. **Diagnose-only — no SPF fix record is ever returned** (see the rule below). |
| `validate_dmarc_record` | `{ record }` | Parsed tags, level'd findings, and whether a pasted DMARC record is valid. No DNS lookup. `upgrade_record` previews a stronger policy and is **capped at `p=quarantine`** — a pasted record carries no alignment evidence, and `p=quarantine` is the ceiling any scan can justify; `p=reject` comes only from the readiness engine's aggregate-report evidence. |
| `generate_dmarc_record` | `{ policy, rua_email?, subdomain_policy?, strict_alignment? }` | A DMARC record built from scratch for a domain that has none, re-validated before it is returned. Use this instead of composing one yourself. |
| `check_dkim_selector` | `{ domain, selector }` | The verdict for ONE specific selector — the exact one the sending platform uses, which a full scan's common-selector sweep may miss. **No fix record**: a DKIM key is generated by the sending platform. |
| `parse_dmarc_report` | `{ content_base64 }` | One DMARC aggregate (RUA) report parsed into readable per-source aggregates — who sent mail as the domain, how much, what share was aligned. XML, `.gz` or `.zip`, up to 2 MiB decoded. Nothing is stored. |
| `check_record` | `{ domain, kind, host? }` | **Did the change land?** Reads the record from the domain's OWN nameservers (cache-free) *and* from two public caching resolvers, and reports whether they agree. `kind` is `spf\|dmarc\|txt\|mx\|cname\|a\|aaaa` — name the kind and the right query is derived for you. Empty `values` means the record is genuinely absent. When `in_sync` is false, `max_wait_seconds` is the largest remaining cached TTL. ⚠️ **Two resolvers is the whole sample — never describe this as worldwide, global, or propagation coverage.** |
| `check_propagation` | `{ name, record_type?, expected_value? }` | **Has the change gone global?** Six vantage points — five owner-run probes across four continents plus this server's own resolver — each read the same name through several resolvers, and the grid comes back with a deterministic `verdict`. Call it after the human publishes a record: **you have ONE network vantage point**, and a record that resolves for you can still be missing elsewhere. `name` is used exactly as given — a leading `www.` is **not** stripped and `_dmarc.example.com` works — so pass the name the record is published at, not the registrable domain. `record_type` is `A\|AAAA\|CNAME\|MX\|TXT\|NS` (SPF and DMARC records are `TXT`). Supply `expected_value` and every cell is reported as match or mismatch against it; omit it and the check reports only whether the vantage points agree with each other. **Observation only — no record is ever composed here.** A cell that did not answer is `unavailable`, which is **not** a negative result, and below three vantage points reached the verdict downgrades to `unknown` — report `vantage_reached` of `vantage_total` rather than calling a name converged on partial coverage. Use `check_record` when the question is only "did my own nameservers take it"; this one answers "is it live everywhere". |
| `lookup_registration` | `{ domain }` | **Who is this domain registered with, and until when?** One RDAP read: registrar, registration/updated/expiry dates, EPP status codes (`clientTransferProhibited`, `pendingDelete`…), nameservers, DNSSEC flag, abuse contact. **Observation only** — no record is ever composed. `status` is `registered`, `not_registered` or `unknown` with a `reason` (`no_rdap_for_tld`, `rate_limited`, `timeout`, `registry_error`, `malformed_response`): **never tell anyone a name is free unless `status` is exactly `not_registered`** — many country domains publish no RDAP and answer `unknown`. `redacted: true` is the post-GDPR norm, a state rather than a failure. |
| `check_reverse_dns` | `{ ip }` | Forward-confirmed reverse DNS (FCrDNS) for one sending IP: the PTR record, the addresses that hostname resolves back to, and a `verdict` of `confirmed`, `ptr_missing` or `mismatch`. **A PTR alone proves nothing** — the IP's operator writes its own reverse zone, so only the forward confirmation is evidence, and **the fix belongs to whoever controls the IP**, never in the sending domain's own DNS. |
| `audit_spf_includes` | `{ domain }` | **Who can transitively send as the domain.** Walks every `include` and `redirect` the SPF record delegates to and returns the resolved tree, per-node lookup attribution, the total authorized IPv4 address count, and typed findings: `include_broken` (a target that no longer publishes SPF — a PermError today), `include_registrable` (a delegated-to domain that does not exist, so a stranger who registers it becomes an authorized sender), `include_expiring` (registration lapsing within 30 days), `pass_all_nested` (a `+all` deep in the chain), `spf_record_unusable` (the audited domain's OWN record is missing or does not parse, so there is no chain to walk). A node the walk could not finish is marked `not_evaluated` rather than dropped. **A domain we could not verify is reported as unverified, never as available** — do not tell anyone a name is free unless the finding is `include_registrable` **and** carries `registry_confirmed: true`; on `registry_confirmed: false` the proof is DNS NXDOMAIN alone, which a name in redemption or on `clientHold` answers identically, so report the broken mechanism and the takeover risk but never call the name available. Findings are risk analysis, not instructions: there is still **no SPF fix record**. Use `count_spf_lookups` instead when the question is only the 10-lookup limit. |
| `build_parked_domain_records` | `{ domain, confirm_no_mail: true, rua_email? }` | The three-record hardening pack that makes a **non-sending** domain unusable for spoofing: a Null MX, a hard-fail SPF record, and a `p=reject; np=reject` DMARC record, in rollout order with a `check_record` verify step each. Parked, redirect and brand-defensive domains only. **Never set `confirm_no_mail` on your own judgment** — see the rule below. The server re-checks DNS itself and returns `records: null` + a `rationale` when it finds evidence of mail; a lookup failure is reported as a failure, never as a pack. |

Two monitoring reads over an account's **own** continuously-monitored domains.
Both need a token (see *Connect to the server*), both are **read-only by
decision**, and both answer from the same cores the dashboard reads, so an agent
and its human are never told different things:

| Tool | Input | Returns |
|---|---|---|
| `get_alerts` | `{ since?, domain?, type?, limit?, before? }` | The account's monitoring alert log, newest first: `id`, `domain`, `type`, `check`, `summary`, a deterministic `detail` map, `created_at`, `email_sent_at`, `acknowledged_at`, `delivery_class`. **Rows carry `delivery_class`** — a `dashboard_only` row was deliberately kept out of the digest mail, so an agent watching only a mailbox sees less than this log holds. **Page down before advancing `since`:** `next_before` is non-null exactly when older rows remain; pass it back as `before` until it is `null`, *then* move your watermark. A caller that takes a full page and jumps `since` to the newest row it saw silently drops every row it never received. `since` is an **inclusive** floor, so rows repeat rather than go missing — de-duplicate on `id`. **No ack, no delete**: acknowledging is the human's own triage on their dashboard, and an agent that acks on their behalf silences a row the human has never seen. |
| `get_readiness` | `{ domain }` | The DMARC enforcement-readiness verdict for ONE monitored domain, computed from its aggregate (RUA) report window: `ready`, `current_step`, `next_step`, `blockers`, the window (`window_days`, `total_messages`, `progress`), `enrollment`, and `next_record` — the validated record for the next step, engine-generated. **`next_record` is `null` while blocked, and that null is an answer:** relay the blockers, never compose a stronger record to fill the gap. Use this before proposing enforcement — a scan shows a domain's *current* policy, but only this evidence window can say whether tightening it would start rejecting real mail. |

An anonymous or invalid-token call to either is refused with the page the owner
mints a token on. Relay that page; do not retry around the refusal, and do not
ask anyone for a credential.

Every domain input is normalized server-side; a malformed domain returns a clean
tool error, never a crash. A `temperror`-shaped tool error means a DNS lookup
timed out — retry, never report it as a verdict. A domain the token's account
does not verifiably own returns the same "not found" as a domain that does not
exist — that is deliberate, and not something to probe around.

## Workflow

1. **Scan.** Call `scan_domain` (fresh) or `get_report` (accept a recent cached
   report). Each check returns a status: `pass`, `warn`, `fail`, `info`, or
   `temperror`.
2. **Read the verdicts failing-first.** Surface `fail` then `warn` then the rest.
   - **`temperror` is transient, NOT a failure** — it means a DNS/network lookup
     timed out. Say "couldn't be resolved right now", never "your SPF is broken".
   - `info` is an honest "not found / not applicable" (e.g. no DKIM selector among
     the ones we probed, or a redacted RDAP expiry) — never report it as a failure.
   - **Check `not_registered` before anything else.** When the report carries
     `not_registered: true`, the domain has no DNS records at all — it is not
     registered, or it has no nameservers. No check ran, so every status is an
     `info` placeholder and **zero failing checks does not mean the domain is
     healthy**. Say the domain does not resolve (a typo is the usual cause),
     propose no SPF/DKIM/DMARC records for it — there is no zone to publish them
     in — and don't offer monitoring until it resolves. The report's `next_steps`
     summary says all of this; relay it.
   - **Read the DMARC check's `details`, not just its status.** One of them can say
     the domain's aggregate reports are going to a third party that has not
     published the RFC 7489 §7.1 authorization record — meaning the reports are
     being **silently discarded** and the owner is collecting nothing, while the
     DMARC record itself still looks correct. It is reported as a detail rather
     than a failure because the domain's own configuration is not at fault; relay
     it anyway, since a DMARC rollout waiting on evidence that never arrives is a
     stall with no visible cause.
3. **Explain the findings** in plain language: what is wrong, why it lets mail be
   spoofed or land in spam, and what fixing it achieves.
4. **Get the fix record.** For DMARC enforcement, call `build_dmarc_upgrade`. It
   derives the alignment gate **server-side** from its own scan — you cannot ask
   it for a stronger rung than the evidence carries. A scan tops out at
   `p=quarantine`: it returns that only when SPF is aligned and a DKIM selector
   was found. **`p=reject` is never scan-derived** — it is unlocked only by the
   readiness engine, from aggregate-report (RUA) evidence collected by monitoring.
   Records are built without `pct`, `rf` or `ri`, which RFC 9989 deprecated.
   - **`record` may be `null`** — when the domain does not exist (there is no zone
     to publish into), when the DMARC lookup itself hit NXDOMAIN while the
     existence probe did not resolve, when the DMARC lookup temp-failed (the
     current record is unknown), when there is no alignment signal at all (the
     answer is reporting first: publish `rua=` and let the evidence accrue, not a
     weaker record), or when the domain already applies a policy at least as
     strong as the one this scan justifies (nothing to change; compared by
     effect — the policy class the record would set — not by bytes). Relay the
     `rationale` as the answer. Do **not** compose a record yourself to fill the
     gap; that is the exact failure this tool exists to prevent.
   - **`policy` describes the returned record, not the domain.** It is `null`
     whenever `record` is `null`; the domain's observed policy is always in
     `current_policy`. Never read `policy` as a recommendation without checking
     `record` first.
5. **Present the record verbatim** (see the rule below) and tell the human to
   publish it in their DNS host.
6. **The human applies it.** DNS Doctor never writes DNS. The human pastes the
   record; then confirm it landed. `check_record` is the cheap verify step — it
   reads that one record from the domain's own nameservers and two public
   resolvers and tells you whether they agree yet, rather than re-running seven
   checks to look at one. When `in_sync` is false the change is real but still
   cached somewhere; `max_wait_seconds` is how long that can last. Once it is
   in sync, re-scan with `scan_domain` to confirm the verdict flipped.
7. **Confirm it went global.** `check_record` and your own resolver are one
   network vantage point between them — a record can be live for you and still
   missing for the receiver in another region, which is exactly the window in
   which a half-propagated DMARC or MX change breaks mail. `check_propagation`
   reads the same name from six vantage points on four continents and
   returns a deterministic `verdict`. Report what it returned: an `unavailable`
   cell is a probe that did not answer, **never** evidence the record is absent
   there, and when `verdict` is `unknown` the coverage was too thin to call —
   say so with `vantage_reached` of `vantage_total` instead of rounding it up to
   "propagated". The page at
   [https://dnsdoctor.dev/tools/dns-propagation-checker](https://dnsdoctor.dev/tools/dns-propagation-checker)
   runs the same check for the human; if you offer it, **print that link
   verbatim as a clickable markdown link** — a link described but not printed
   never reaches them.

## The one rule you must not break

**Present any returned record string exactly as given. Never rewrite, reformat,
re-wrap, "clean up", or "improve" it.** The record was generated and validated by
the fix engine — a wrong SPF or DMARC record still *parses as valid* and fails
silently, so an "improvement" can silently de-authorize a real sender or weaken
enforcement with no error anywhere. Copy the exact bytes. If a record looks
unusual, that is the validated form; do not second-guess it.

A DKIM key is generated by the sending platform, not by DNS Doctor — for DKIM
findings, point the human at their email provider's DKIM setup, don't fabricate a
key.

SPF is likewise diagnose-only: DNS Doctor reports SPF problems but deliberately
emits **no** SPF fix record, because an auto-"fix" can silently de-authorize a
real sender. Relay the report's SPF findings; do not propose SPF edits of your
own (e.g. switching `~all` to `-all`). The one SPF record DNS Doctor ever emits
is the constant `v=spf1 -all` inside the parked-domain pack, for a domain the
server itself verified sends no mail — that is the whole carve-out, and it does
not extend to any domain that sends.

**Never assert that a domain sends no mail yourself.** `confirm_no_mail` is the
human owner's statement, not an inference you may draw from a quiet scan: a
domain with one legacy or transactional sender looks identical in DNS to a
parked one until you ask. The flag unlocks the question, not the answer — the
server re-checks existence, MX, SPF and DKIM selectors and refuses with a
`rationale` when it finds evidence of mail. Relay that rationale; do not retry
around it.

**DMARC records DNS Doctor generates carry `np=reject`** (DMARCbis, RFC 9989)
whenever the input has no explicit `np` of its own. A subdomain that does not
exist in DNS cannot have published SPF or DKIM records, so it can have no
aligned legitimate mail — the tag is safe at any org-domain policy, and
receivers that predate RFC 9989 ignore it and fall back to `sp`/`p`. If a record
already sets `np`, that value is preserved untouched. It is part of the
validated record: present it verbatim like the rest.

## DMARC enforcement takes time — set expectations honestly

Moving to `p=reject` safely needs roughly 30 days of RUA (aggregate report)
evidence that every legitimate sender is aligned — which a session-bound assistant
cannot watch. Apply fixes only after the domain's owner approves them. If the user
wants the domain watched continuously (RUA dashboard + alerts), call
`start_monitoring_signup` and **give the human the `signup_url` it returns —
printed verbatim as a clickable markdown link on its own line; never paraphrase,
shorten, or describe it without printing it** (a link described but not printed
never reaches them). The call sends no email and creates nothing — you are
proposing, not committing them.

**When your host supports account linking, there is a shorter path.** Call
`add_monitored_domain` — the human approves the connection once on our page, and
from then on you can read the records they need, publish them with a DNS tool of
your own (showing them exactly what you are about to add and getting their
approval first) or hand them the copy-paste, and confirm the result with
`check_domain_verification`, all without them leaving the conversation. If
linking is unavailable, `start_monitoring_signup` is the path above and it still
works. Either way **nothing is applied to anyone's DNS by us** — a human
publishes every record.

**Never ask a human for their email address to pass to us, and never invent one.**
Hand over the link and let them sign in on our page themselves — the page offers
whichever sign-in methods are available (a social provider or an emailed link).

**Do not promise that opening the link starts monitoring.** Signing in creates
their free account and carries the domain over to their dashboard, already
filled in — that is all it can do.
Daily monitoring is gated on proving they control the domain, so they finish by
publishing a TXT record the dashboard shows them. The tool's own `message` says
this; relay it verbatim rather than paraphrasing it into "we're now watching your
domain".

What continuous monitoring is, so you can describe it accurately: once a domain
is verified, DNS Doctor re-scans it daily and keeps the **history** of every
check, emails **alerts** when a verdict changes or a new sending source appears,
ingests the domain's DMARC **aggregate (RUA) reports**, and derives an
enforcement **readiness** verdict from them — the 30-day alignment evidence this
session cannot gather. That is what `start_monitoring_signup` hands off to; none
of it happens from a one-off scan.

Once the domain is verified and the owner has given their agent a token, the
alerts and the readiness verdict are readable through `get_alerts` and
`get_readiness` — see playbook 5. That is how a later session picks the domain
back up without re-deriving anything.

## Playbooks

Five read-orders over the tool surface. Each is an evidence sequence: run the
step, read what it rules in or out, and stop when the evidence answers the
question. **Report what the tools returned.** Do not estimate how much mail is
affected, how likely a problem is, or what a fix will improve by — DNS Doctor
returns evidence, and a number you invented beside it reads as though the
scanner produced it.

### 1. Pre-migration audit — before moving email providers

1. `scan_domain` the domain. Note the current SPF, DKIM and DMARC verdicts as
   the "before" state; this is what you will compare against after the cutover.
2. `audit_spf_includes`. The tree names every provider the domain currently
   delegates authorization to — including the ones nobody remembers adding.
   Decision point: each include belongs to a sender that is either staying,
   going, or unknown. Ask the owner which; do not guess from the vendor name.
3. Read the findings before planning any edit. `include_broken` is already
   failing today and is not caused by the migration. `pass_all_nested` means
   some delegated record authorizes the whole internet — flag it now, because
   the migration is the moment to drop that include. `include_registrable` and
   `include_expiring` name includes that are unsafe to carry over.
4. `count_spf_lookups` to see the lookup cost of the current record, and what
   headroom the new provider's include needs. The limit is 10 and it is counted
   recursively.
5. Write the cutover checklist with the owner: which includes are removed, which
   are added, what DKIM selectors the new provider publishes, whether DMARC
   policy should be relaxed during the move. **You propose; they decide.**
6. After each DNS change lands, `check_record` that one record (`spf`, `dmarc`,
   or `txt` for a selector) to confirm it is live and in sync, then `scan_domain`
   once the whole cutover is done to compare against step 1.

### 2. Deliverability triage — "our mail isn't arriving"

Work the evidence in this order and say explicitly what each step rules out.

1. `scan_domain`. If `not_registered` is true, stop — the domain does not
   resolve and nothing below applies.
2. **Authentication first.** Read SPF, DKIM and DMARC. A `fail` here is the
   most common cause and the cheapest to fix. `temperror` rules nothing out —
   it means the lookup did not complete; retry before drawing any conclusion.
3. **Blacklist.** A listing explains rejection at the receiving edge even when
   authentication is perfect. A clean result rules the blocklists we query out,
   and only those.
4. **MX.** Check the receiving side if inbound mail is the complaint. Outbound
   problems are unaffected by MX — say so rather than reporting it as a finding.
5. **Alignment.** SPF or DKIM passing is not enough for DMARC: the passing
   identifier must align with the From domain. `build_dmarc_upgrade`'s
   `rationale` states what the server found; the report's DMARC detail lines
   name misaligned or unauthorized sources.
6. **Sending IP.** If a specific IP is being rejected, `check_reverse_dns` on
   it. `ptr_missing` or `mismatch` is a real deliverability cause — and the fix
   belongs to whoever operates the IP, not in the domain's DNS.
7. If the evidence is exhausted and mail still is not arriving, say that
   plainly: DNS-observable checks cannot see content filtering, reputation, or
   the receiving side's policy. Do not fill the gap with a guess.

### 3. Getting to `p=reject`

1. `scan_domain` for the current policy, then `build_dmarc_upgrade`. The server
   derives the alignment gate itself: a scan tops out at `p=quarantine`, returned
   only when SPF is aligned and a DKIM selector was found. With no alignment
   signal it returns no record at all and says to publish reporting first —
   `p=reject` is reachable only through the readiness engine's aggregate-report
   evidence, never from a scan.
2. If `record` is `null`, the `rationale` is the answer — including "already at
   least as strong". Relay it; never compose a record to fill the gap.
3. Present the returned record verbatim; the human publishes it, then
   `check_record` with `kind: dmarc` confirms it landed.
4. **Be honest about the next step.** The rung after `p=quarantine` needs
   roughly 30 days of aggregate-report evidence that every legitimate sender is
   aligned. You cannot watch that within a session, and there is no shortcut
   that makes `p=reject` safe sooner.
5. If the owner has RUA files already, `parse_dmarc_report` reads one report and
   shows the per-source aligned share. One report is one window from one
   receiver — useful evidence, not a readiness verdict.
6. For the 30-day watch, `start_monitoring_signup` and hand over the
   `signup_url` — printed verbatim as a clickable markdown link on its own
   line, never merely described. That is what supplies the readiness verdict
   this step needs.

### 4. Parked-domain sweep (MSP / multi-domain)

1. Start from the owner's list of domains they believe send no mail — brand
   defensives, redirects, old acquisitions. **The list comes from them.** You
   may not classify a domain as parked from a scan.
2. Per domain, `scan_domain` first. It gives you the state to show the owner,
   and `not_registered` domains drop out of the sweep immediately.
3. Ask the owner to confirm, domain by domain, that it sends nothing —
   including transactional mail, monitoring alerts, and any one legacy system.
   Only then call `build_parked_domain_records` with `confirm_no_mail: true`.
4. Read the response before presenting anything. `records: null` means the
   server found DNS evidence of mail; relay the `rationale` and remove that
   domain from the sweep. A transient failure is a retry, never a pass.
5. On a pass, present the three records verbatim in the order given, with each
   record's purpose and its `check_record` verify step. Publishing is the
   human's decision, one record at a time if they prefer.
6. After publishing, `check_record` per record (`mx`, `spf`, `dmarc`) to confirm
   each is live and in sync. Passing `rua_email` on the build puts spoof
   attempts against the parked domain into aggregate reports.

### 5. The operate loop — watching a monitored domain over time

This is the sequence for a domain that is already (or is about to be) under
continuous monitoring. It needs an API token for steps 3–5; the owner mints one
at `/dashboard/settings` and gives it to their agent's environment, never to you
in a message.

1. **Enroll.** `start_monitoring_signup` and hand the human the `signup_url`,
   printed verbatim as a clickable markdown link on its own line — never
   described without being printed. You are proposing, not committing them; the
   call creates nothing.
2. **Verify.** They sign in and publish the TXT ownership record their dashboard
   shows them. Daily monitoring starts only once that verification passes —
   until then there is nothing to read, and `get_alerts` / `get_readiness` will
   not find the domain.
3. **Watch.** `get_alerts` on a cadence that suits the human, filtered by
   `domain` or `type` when they only care about one. Page down with `before`
   until `next_before` is `null` before advancing `since`, and de-duplicate on
   `id`. Report what the log says; the human clears it.
4. **Check readiness before proposing enforcement.** `get_readiness` for the
   domain. `ready: false` means the `blockers` are the answer — an unaligned
   source, too little evidence, an empty window. Say which, and say what would
   clear it.
5. **Propose.** When readiness allows it, `build_dmarc_upgrade` produces the
   validated record. `get_readiness`'s own `next_record` is the same engine
   output for the next step. Either way: present it verbatim, never compose one,
   and never treat a `null` record as a gap to fill.
6. **The human approves and applies.** DNS Doctor never writes DNS. After they
   publish, `check_record` with `kind: dmarc` confirms it landed and is in sync.
7. **Re-scan and re-read.** `scan_domain` confirms the verdict flipped;
   the next `get_readiness` window shows the effect of the change over time.
   Then back to step 3 — the loop is the product.

Two things this loop must not do: acknowledge alerts on the human's behalf
(there is no such tool, deliberately), and treat a quiet `get_alerts` page as
proof a domain is healthy when you skipped a page to get there.

## Learn more

The scoring methodology (how SPF lookups are counted, why `p=reject` needs an
alignment signal, what "temperror ≠ fail" means) is published at
<https://dnsdoctor.dev/methodology>.
Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package author
Andriy Shendrikov

Package observed Oct 2, 2026.

Technical details
First seen
Sep 30, 2026 · 22:02 UTC
Last seen
Oct 2, 2026 · 00:00 UTC
Collection status
Collected

plugin_asdk_app_6a60c62613808191b41e39b40625b99f

Download plugin data (JSON)