Install
openclaw skills install @dnsdoctor/dns-doctorUse when a domain's email is landing in spam, or you see 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 domain that might be blacklisted. Diagnoses SPF, DMARC, DKIM, MX, DNS, blacklist and domain/SSL expiry via the DNS Doctor public API and returns copy-paste fix records generated by a validating engine — never a guessed record.
openclaw skills install @dnsdoctor/dns-doctorDNS Doctor scans a domain's email-authentication posture 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.
Reach for it whenever a user describes any of:
550 5.7.515, 550 5.7.1, or dmarc=failBase: https://dnsdoctor.dev/api/v1 (schema: https://dnsdoctor.dev/api/v1/openapi.json).
# Fresh scan (re-scanning the same domain within a minute reuses the report):
curl -s -X POST https://dnsdoctor.dev/api/v1/scan \
-H 'Content-Type: application/json' -d '{"domain": "example.com"}'
# Persisted report (scans once if none exists):
curl -s https://dnsdoctor.dev/api/v1/report/example.com
Each check in the response carries check, status, title, an optional
explanation, the observed raw record, and — when a fix exists — a
fix_record string generated by the validating engine. Anonymous calls are
rate-limited per IP; a 429 means slow down, not failure.
An API token (Authorization: Bearer dnsd_…, free account) raises that limit and
is required for the three reads that return one account's own monitoring
data: GET /api/v1/domains, GET /api/v1/alerts and GET /api/v1/readiness.
Without a valid token those three answer 401 — and the body of that refusal is
the guidance, so relay it rather than paraphrasing. Everything else on this page
works anonymously. You cannot create that token — the account owner mints it
while signed in at /dashboard/settings, and a refused call names the page.
Relay the link; never ask anyone to paste a credential to you. An MCP server
with the same engine is at https://dnsdoctor.dev/mcp if your setup speaks MCP.
For a domain the account already monitors and has verified — nothing here is readable before the TXT ownership record is published and verification passes.
# The account's alert log, newest first:
curl -s -H "Authorization: Bearer $DNSDOCTOR_API_TOKEN" \
'https://dnsdoctor.dev/api/v1/alerts?limit=50'
# Enforcement readiness for one monitored domain:
curl -s -H "Authorization: Bearer $DNSDOCTOR_API_TOKEN" \
'https://dnsdoctor.dev/api/v1/readiness?domain=example.com'
alerts takes since (an inclusive ISO-8601 created_at floor), domain,
type, limit (1..100, default 50) and before (an opaque cursor). Rows carry
id, domain, type, check, summary, a deterministic detail map,
created_at, email_sent_at, acknowledged_at and 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 comes back null, and
only then move your watermark. A caller that takes a full page and jumps
since to the newest row it saw drops every row it never received — silently.
Because since is inclusive, rows repeat rather than go missing: de-duplicate
on id.
Read-only by decision — there is no ack and no delete on this API. Acknowledging an alert 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. Report what the log says; let them clear it.
readiness takes one required domain and returns ready, current_step,
next_step, blockers, the evidence window (window_days, total_messages,
progress), enrollment, and next_record — the validated record for the next
step, generated by the engine. next_record is null while blocked, and that
null is an answer: relay the blockers and never compose a stronger record to
fill the gap. Ask this before proposing enforcement — a scan shows the domain's
current policy, but only this window says whether tightening it would start
rejecting real mail.
Both return 422 on a malformed domain, unknown type or bad cursor, and the
same opaque 404 for a domain the token's account does not verifiably own as for
one that does not exist. That opacity is deliberate; do not probe around it.
https://dnsdoctor.dev/api/tools/…, all POST JSON)For the single questions a full scan over-answers — same engine, no auth:
# Did the change land? (kind: spf|dmarc|txt|mx|cname|a|aaaa)
curl -s -X POST https://dnsdoctor.dev/api/tools/check-record \
-H 'Content-Type: application/json' -d '{"domain": "example.com", "kind": "dmarc"}'
# Forward-confirmed reverse DNS for one sending IP:
curl -s -X POST https://dnsdoctor.dev/api/tools/reverse-dns-check \
-H 'Content-Type: application/json' -d '{"ip": "203.0.113.10"}'
check-record reads the record from the domain's OWN nameservers (cache-free)
and from two public caching resolvers, returning in_sync plus
max_wait_seconds — the largest remaining cached TTL. Empty values means the
record is genuinely absent. ⚠️ Two resolvers is the whole sample: never
describe it as worldwide, global, or propagation coverage.
reverse-dns-check returns a verdict of confirmed, ptr_missing or
mismatch, and shows the addresses the PTR hostname resolved back to. 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 the sending domain's own DNS.
Also available: /api/tools/spf-count (SPF lookups against the RFC 7208 limit
of 10 — diagnose-only, no fix record), /api/tools/dmarc-validate (a pasted
record's tags + findings; its upgrade_record is capped at p=quarantine,
since a pasted record carries no alignment evidence), /api/tools/dmarc-generate
(a record built from scratch and re-validated), /api/tools/dkim-check (one
specific selector — no fix record; the key comes from the sending platform) and
/api/tools/dmarc-report-parse (one aggregate report → per-source aggregates;
nothing is stored). A 503 from any of them is a transient resolver fault —
retry; it is never a verdict.
# Who can transitively send as this domain — walks the whole include tree:
curl -s -X POST https://dnsdoctor.dev/api/tools/spf-audit \
-H 'Content-Type: application/json' -d '{"domain": "example.com"}'
# The hardening pack for a domain that sends NO mail:
curl -s -X POST https://dnsdoctor.dev/api/tools/parked-domain-records \
-H 'Content-Type: application/json' \
-d '{"domain": "example.com", "confirm_no_mail": true}'
spf-audit resolves the full include/redirect tree and reports what a lookup
count cannot: includes that are broken today, an include target whose registrable
domain is confirmed unregistered (anyone could register it and become an
authorized sender), targets expiring within 30 days, and a +all nested anywhere
in the tree. It also totals the IPv4 addresses the record transitively authorizes.
An unresolved edge is reported as not_evaluated, never dropped, and registration
is called absent only on confirmed evidence — any lookup fault reports
"unverified", never "available". Diagnose-only, like every SPF surface here: it
returns no fix record.
parked-domain-records returns three records for a non-sending domain — Null
MX, v=spf1 -all, and _dmarc at p=reject; np=reject. confirm_no_mail: true
unlocks the question, not the answer: the server independently checks the domain
from DNS (existence, MX, pass-capable SPF mechanisms, a DKIM selector sweep) and
answers 200 with records: null plus a rationale if it finds any evidence of
mail. Any transient lookup failure refuses too, because this output ends in -all
and a wrong one silently de-authorizes a real sender. Never set the flag on your
own judgement — ask the human who owns the domain, and treat a refusal as the
answer rather than something to work around.
POST /scan (fresh) or GET /report/{domain} (accept recent).
Statuses per check: pass, warn, fail, info, or temperror.fail, then warn, then the rest.
temperror is transient, NOT a failure — 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 probed ones, or a redacted RDAP expiry) — never a failure.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.fix_record from the report. The
DMARC upgrade is alignment-gated server-side: the engine emits p=reject
only when the scan shows aligned SPF and a DKIM selector; otherwise it caps
at p=quarantine. A check with no fix_record has no honest fix to offer —
relay its explanation instead, and do not compose a record yourself to
fill the gap; that is the exact failure this service exists to prevent./api/tools/check-record — one record read
instead of a seven-check re-scan; in_sync: false means the change is real
but still cached somewhere. Once in sync, re-scan to confirm the verdict
flipped.The DMARC check's details can report external RUA authorization (RFC 7489 §7.1): when the domain sends aggregate reports to a third-party domain that has not published the authorization record, those reports are silently discarded — the DMARC record still looks correct while the owner collects nothing. Reported as a detail, never a status change (the domain's own config is not at fault), but relay it: a rollout waiting on evidence that never arrives is a stall with no visible cause.
Present any returned record string exactly as given. Never rewrite, reformat, re-wrap, "clean up", or "improve" it. 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.
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; never 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).
Moving to p=reject safely needs roughly 30 days of aggregate-report (RUA)
evidence that every legitimate sender is aligned — which a session-bound
assistant cannot watch. Apply fixes only after the domain's owner approves. If
the user wants the domain watched continuously (RUA dashboard + alerts), give
them this link and ask them to open it themselves:
https://dnsdoctor.dev/start?domain=example.com&ref=agent
Never ask the 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).
Opening it sends no email and creates nothing: the page explains what monitoring does and asks them to sign in themselves. 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, and daily monitoring starts only after they prove control by publishing a TXT record the dashboard shows them.
Once that is done and the owner has put a token in your environment, the loop
over time is: GET /v1/alerts on a cadence (paging down with before until
next_before is null before advancing since) → GET /v1/readiness before
proposing enforcement → present the returned record verbatim → the human
publishes → /api/tools/check-record to confirm it landed → re-scan. The reads
are the watch; every change is still theirs to approve.
How the verdicts are computed (SPF lookup counting, why p=reject needs an
alignment signal, "temperror ≠ fail") is published at
https://dnsdoctor.dev/methodology.