Skip to content
Deliverability

How to check your SPF, DKIM and DMARC records (step by step)

SPF and DMARC sit at names you can guess. DKIM does not, and that single asymmetry explains why most auth checkers report unknown instead of a verdict.

9 Apr 2026 15 min readBy Autocloz Editorial, Deliverability team
How to check your SPF, DKIM and DMARC records (step by step)

Checking SPF and DMARC is a DNS lookup at a name you already know: the domain itself for SPF, and _dmarc. plus the domain for DMARC. Checking DKIM is not, because a DKIM key lives at ._domainkey.yourdomain.com and nothing in DNS lets you list the selectors a domain uses. You either know one or you read it off a message you already sent. And no lookup of any of the three can tell you whether DMARC passes, because alignment is a property of a message rather than of a record. Three commands, one header, in that order.

The three records are not three of the same thing

People say "check your SPF, DKIM and DMARC" as if it were one task repeated three times. It is three different tasks with three different failure modes, and conflating them is why so many domains sit on a green dashboard while their mail fails authentication.

SPF is a single TXT record at the domain's own name. It is discoverable: you know where to look, and either it is there or it is not. Its interesting failure is not absence — it is a record that exists, reads correctly, and still evaluates to a permanent error because of what it references.

DMARC is a single TXT record at a fixed prefix, _dmarc, in front of the domain. Also discoverable. Its interesting failure is a policy that says something weaker than the operator believes, usually because of a tag they did not know was there or a tag that no longer means what it used to.

DKIM is not one record and is not discoverable. A domain can carry any number of keys, each at its own selector, and the DNS has no directory operation — you cannot ask a zone to list what is under _domainkey. Its interesting failure is that a checker reports nothing at all and you cannot tell absence from ignorance.

That asymmetry drives everything below.

Step one: read the SPF record, then count what it costs

The command is one line:

dig TXT yourdomain.com +short

You are looking for exactly one string beginning v=spf1. Two of them is a configuration error, and receivers are entitled to treat the result as a permanent error rather than picking one — which means publishing a second SPF record to "add" a vendor removes authentication for every vendor at once.

Then read the last mechanism. A record ending -all is a hard fail for anything not listed. ~all is a soft fail, which most receivers treat as a signal rather than a refusal. ?all is neutral and asserts nothing, so a record that ends there is doing no work. +all authorises the entire internet to send as your domain and should never appear on a domain you own.

Now the part almost nobody does by hand. RFC 7208 section 4.6.4 caps SPF evaluation at ten DNS-querying terms. The terms that count are include, a, mx, ptr, exists and redirect. The cap applies to the whole evaluation, not to your record, so every include: you publish drags in whatever that vendor's record references, and their nested includes count against your ten. Exceed it and receivers return permerror, at which point SPF contributes nothing — not a soft fail, not a warning, nothing — while your record still reads perfectly when you look at it.

Counting it correctly means recursion. Fetch your record, count its querying terms, then fetch each include: target's record and count that one, and repeat. A four-include record with three vendors that each publish two includes of their own is already at ten before you have added anything. The SPF and DMARC record checker does the count on a record you paste in, which is the fastest way to answer the one question reading the record cannot.

The reason this failure is so persistent is that it has no symptom on your side. Nothing bounces. Nothing logs. The record does not change. A vendor you added eighteen months ago expands its own include chain, your evaluation crosses ten, and from that day every message you send authenticates on DKIM alone.

Step two: DKIM cannot be looked up unless you already know the selector

A DKIM public key is published at ._domainkey. as a TXT record. The selector is chosen by whoever generated the key, appears in the s= tag of the signature the sender adds to each message, and is otherwise unadvertised. DNS offers no way to enumerate the names under a label. So the question "does this domain have DKIM?" has no answer from DNS alone.

There are exactly three honest ways to get a selector.

Read it off a message. Send one to a mailbox you control, open the raw source, and find the DKIM-Signature header. It contains d= for the signing domain and s= for the selector. This is the authoritative answer, because it is the selector actually being used on live mail rather than one you were told about during setup.

Ask the platform that generated the key. Google Workspace uses google by default. Microsoft 365 publishes selector1 and selector2 as CNAMEs. Amazon SES Easy DKIM is the awkward case: it publishes three CNAMEs at randomly generated token names pointing at dkim.amazonses.com, so no static list of common selectors can ever match an SES domain. That is not a gap in any particular checker — it is a consequence of how SES issues keys.

Guess, and treat a miss as unknown. This is what a generic checker does, and it is legitimate as long as it is honest about the result. Autocloz probes a bounded list of mainstream selectors and, when the probe came from guesses rather than from a selector the operator supplied or from a connected SES identity's own DKIM tokens, reports the result as unknown and never as fail. A miss on a guess is not evidence.

Once you have a record, two things in it are worth reading.

An empty p= means the key is revoked. RFC 6376 section 3.6.1 defines this: publishing an empty public key is how an operator retires a selector cleanly. If a selector you believe is live returns an empty p=, signing on that selector is broken.

The key length is recoverable from the p= value, which is a base64-encoded public key. Sub-1024-bit RSA keys are treated as weak by major receivers; 1024 is the practical floor and 2048 the current expectation. A key that was generated once during setup and never touched since is worth checking rather than assuming.

Step three: read the DMARC record tag by tag, and know which tags still exist

dig TXT _dmarc.yourdomain.com +short

One string beginning v=DMARC1. Then read the tags, and read them knowing that the specification changed in May 2026.

p= is the policy for the domain itself: none, quarantine or reject. A record with p=none is a monitoring configuration and asks receivers to do nothing differently.

sp= is the policy for subdomains. Absent, subdomains inherit p. Present and set to none, a p=reject domain protects its own name while leaving every subdomain beneath it unprotected — which is the single most common way a domain that looks fully enforced is not.

rua= is the address aggregate reports are sent to. Absent, you get no reports, which means no evidence about which of your senders are failing.

adkim= and aspf= control alignment strictness, defaulting to relaxed. Relaxed alignment accepts a subdomain of your organisational domain; strict requires an exact match.

Two tags changed with RFC 9989, published in May 2026 alongside RFC 9990 and RFC 9991 for aggregate and failure reporting. Together they obsolete RFC 7489 and move DMARC from Informational to Proposed Standard for the first time.

pct= is gone. RFC 7489 allowed a policy to be applied to a sampled percentage of mail, which is how staged rollouts were done. RFC 9989 removes the tag from the specification entirely. Receivers that have not yet moved will still honour it, so a record carrying pct= is not broken — but it is a control on its way out, and a staged rollout planned around it has a shrinking shelf life. Checkers built against RFC 7489, including the one in Autocloz, still parse pct and still default it to 100 when absent, which is the correct behaviour for the receivers that have not moved and the wrong basis for a plan.

np= is new: a separate policy for non-existent subdomains of the organisational domain. It matters if you have registered lookalike domains or park subdomains that should never send.

The other structural change is how the organisational domain is found. RFC 7489 leaned on the Public Suffix List; RFC 9989 replaces it with a DNS tree walk capped at eight queries. For a plain second-level domain the practical outcome is identical. For anything with an unusual shape — a domain under a multi-label public suffix, or a delegated namespace — it is not, and what DMARC actually instructs a receiver to do works through the mechanics in detail.

The check no DNS lookup can do: alignment

Every command above tells you what you published. None of them tells you whether a message passes. That gap is where working setups quietly break, and it has one cause: DMARC does not care whether SPF or DKIM passed. It cares whether a passing mechanism aligns with the domain the recipient sees in the From header.

SPF is evaluated against the envelope sender — the address in MAIL FROM, which surfaces as the Return-Path header. On most sending platforms that address belongs to the platform's own bounce domain, not to you. So SPF passes, correctly, for a domain that is not yours, and contributes nothing to alignment. Everything then rests on DKIM's d= matching your From domain.

The only way to observe this is to look at a message. Send one to a mailbox you control on a different provider, open the original source, and find the Authentication-Results header, which RFC 8601 defines as the receiving server recording what it concluded. You want three things in it: spf=pass, dkim=pass, and dmarc=pass with header.from showing your own domain.

The diagnostic pattern to memorise is spf=pass sitting next to dmarc=fail. That is not an SPF problem. Something authenticated; it just was not your domain. Check the Return-Path header on the same message and you will usually find the platform's bounce domain staring back at you.

Autocloz's free plan covers 5 users and 10 mailboxes, with SPF, DKIM and DMARC monitoring on every connected sending domain and a re-check that runs on its own rather than only when someone clicks refresh — start free if you would rather be told when a record drifts than find out from a bounce.

The exact sequence, and what a healthy answer looks like

Run these in order. Each one is fast, and each answers a question the next one assumes.

  1. dig TXT yourdomain.com +short — one v=spf1 string, ending -all or ~all.
  2. Count the querying mechanisms in it, recursing into every include: and redirect=. Total must be ten or fewer.
  3. dig TXT _dmarc.yourdomain.com +short — one v=DMARC1 string, with a p= you meant and an rua= you read.
  4. Find your selector in the DKIM-Signature header of a sent message, then dig TXT ._domainkey.yourdomain.com +short — a v=DKIM1 record with a non-empty p=.
  5. Send one message to a mailbox you control elsewhere and read Authentication-Results for dmarc=pass with your domain in header.from.

Steps one to four take about two minutes. Step five is the only one that produces evidence about mail rather than about DNS, and it is the one people skip.

Six answers that look fine and are not

Each of these returns something. None of them means what it appears to mean.

Two records beginning v=spf1. Both look valid individually. Together they are a permanent error, and the domain authenticates on DKIM alone.

An SPF record ending ?all. Syntactically perfect, semantically empty. It asserts that the domain expresses no opinion about senders not listed.

v=DMARC1; p=reject; sp=none. Reads as maximum enforcement in every summary tool that only prints p. Every subdomain beneath it is unprotected.

A DKIM record with p= and nothing after it. Present in DNS, and revoked by definition. A checker that only tests for the record's existence will call this a pass.

An empty answer for a selector you were given during setup. This is genuinely ambiguous, and the ambiguity is the point: it means either the key was never published, or the platform rotated to a different selector and nobody updated the note. Read the signature on a real message rather than trusting the setup document.

A DMARC record with a rua= address at a domain you no longer control. Reports are being generated and delivered to somebody. Not to you.

The four records people check next, and which of them touch cold email

Once the three are green, a checker will start offering you more names. They are not equally worth your time, and two of them are about your infrastructure rather than your domain.

PTR, with forward confirmation. A sending IP should have a reverse-DNS record whose hostname resolves back to the same IP. Google's sender guidelines require valid forward and reverse DNS records for every sender, not only bulk ones. The check is two lookups, not one: read the PTR for the IP, then resolve the hostname it gives you and confirm you land back on the same address. A PTR that exists but does not forward-confirm is the failure mode worth knowing about, because a single lookup reports it as present. If you send through a hosted provider you do not control this record, and there is nothing to fix — check it anyway so you know which category you are in.

MTA-STS, defined in RFC 8461. Two parts: a TXT record at _mta-sts. advertising a policy id, and the policy itself served over HTTPS at mta-sts./.well-known/mta-sts.txt declaring a mode of enforce, testing or none. It governs how other servers connect to *your* mail, so it is about receiving rather than sending. Worth publishing on a domain that receives real mail; irrelevant to whether your outbound authenticates.

TLS-RPT, defined in RFC 8460. A TXT record at _smtp._tls. with a rua= destination, which asks other servers to send you reports about failed TLS negotiations with your mail hosts. Diagnostic value, no placement value.

BIMI. A TXT record at default._bimi. carrying l= for a logo SVG and optionally a= for a Verified Mark Certificate. It controls whether a brand logo renders beside your name in supporting inboxes. Gmail requires the VMC, which costs money annually, and BIMI requires an enforcing DMARC policy before any of it applies. For cold outreach from a secondary sending domain this is close to pointless, because the whole value is brand recognition and the domain is deliberately not your brand.

Checking a domain you do not control

The same commands work on any domain, and there is a legitimate reason to run them: before you route mail through a partner, before you trust a vendor's sending setup, or when you are trying to work out why a supplier's invoices keep landing in your own spam folder.

Everything in steps one to four above works from outside, because all of it is public DNS. Two things do not. You cannot enumerate their DKIM selectors any more than you can your own, so unless you have a message from them to read the signature off, DKIM is unknowable. And you cannot read their Authentication-Results, because that header is written by *your* receiving server on *your* copy — which is exactly why a message from them, sitting in your mailbox, is the most informative artefact you can get hold of. Open one and read the header your own provider stamped on it.

What Autocloz does not do here, and what a record check cannot prove

Being exact about the boundary is more useful than another paragraph of reassurance.

A record check proves what you published. It does not prove that a given message authenticated, that a given receiver agreed with your policy, or that mail from your domain reached anyone's inbox. Acceptance, authentication and placement are three separate outcomes, and DNS speaks to none of the last two. Placement testing and what it can and cannot see covers the instrument that answers the third question.

Autocloz monitors SPF, DKIM and DMARC on connected sending domains and re-checks stale monitors on a background sweep rather than only on demand, but it cannot enumerate DKIM selectors it was never told about, for the reason set out above — no tool can. Supply the selector or connect the sending account and the check becomes authoritative; leave it to guess and an honest unknown is the best answer available.

It does not fix records for you. It reads DNS, classifies what it finds, and tells you which of the failures above you have; publishing the corrected record is a change in your DNS provider that no third party should be making on your behalf. If you want the records generated first, the SPF, DKIM and DMARC record generator produces them for a given provider, and the step-by-step publishing order for a cold sending domain covers what to do after they resolve.

And no configuration of any of this settles where a message lands. Authentication is a gate you must be through before the interesting decisions are made about you; it is not itself an outcome. If you are choosing between doing this yourself and paying a standalone deliverability service to watch it, the honest comparison is on the Folderly alternatives page, where the feature that actually differs is monitoring cadence rather than the checks themselves.

Frequently asked

Why does my DKIM checker say unknown instead of pass or fail?

Because DKIM keys are published at a name that contains a selector, and there is no DNS mechanism for listing the selectors a domain uses. A checker that has not been told your selector can only guess from a list of common ones, and a miss on a guess proves nothing. The honest verdict for a missed guess is unknown, not fail. Find your real selector by reading the s= tag in the DKIM-Signature header of a message you have already sent.

How do I know whether my SPF record is over the ten-lookup limit?

Count every mechanism in your record that triggers a DNS query — include, a, mx, ptr, exists and redirect — and then recurse into each include and redirect target and count theirs too. RFC 7208 section 4.6.4 caps the total at ten during evaluation. Over the cap, receivers return permerror and your SPF authenticates nothing, even though the record still looks correct when you read it.

Does the pct tag still work in a DMARC record?

RFC 9989, published in May 2026, removes the pct tag from the DMARC specification entirely and obsoletes RFC 7489, which defined it. Receivers that have not yet moved to the new specification will still honour pct, so a record carrying it is not broken today. Treat it as a tag on its way out rather than a control you can rely on for a staged rollout.

What does an empty p= value in a DKIM record mean?

An empty public key field marks the key as revoked, per RFC 6376 section 3.6.1. Operators publish an empty p= when retiring a selector so that any message still carrying a signature from that key fails verification cleanly rather than ambiguously. If you find an empty p= on a selector you believe is live, signing on that selector is broken and every message it signs will fail DKIM.

Can a DNS lookup tell me whether DMARC passes for my mail?

No. A DNS lookup tells you what policy you published. Whether DMARC passes for a given message depends on alignment — whether the domain that passed SPF or the domain in the DKIM d= tag shares an organisational domain with the address in the visible From header. That is a property of a message, so the only way to check it is to send one to a mailbox you control and read its Authentication-Results header.

Share
Free to start

Stop reading. Start sending.

Every tactic in this article is implemented behind the Autocloz dashboard.