🛡️ Email authentication checks explained

What the email module tests on your sender identity: SPF, DKIM, DMARC (RFC 9989), BIMI, MTA-STS and TLS-RPT – why each record matters and how it is scored. Check your domain → Need to create one of these records? Use the free Record Studio for SPF, DKIM, DMARC, BIMI, MTA-STS and TLS-RPT.

Last updated: September 2026 · 28 checks documented

Evaluated domain (www → apex) #

E-mail authentication (MX, SPF, DKIM, DMARC, MTA-STS) is configured at the domain itself; www. is merely the website alias and practically never sends or receives mail. Checking www directly would mostly produce meaningless "missing" findings.

RatingInformational only (weight 0), and shown only when the rewrite actually happened: when the input starts with www. AND the input is not itself the registrable domain, exactly that one label is stripped and the parent domain is evaluated. The second condition matters – www.de is a real domain, and stripping there would collapse the scan onto the bare TLD. The registrable domain comes from the Public Suffix List; when it cannot be determined, nothing is rewritten. Other subdomains are NOT rewritten – they can legitimately run their own mail identity and get the host-specific evaluation instead. No standard defines this rewrite; it follows from where the records live: SPF, DKIM, MX and MTA-STS are looked up at the exact name, so evaluating www. would report the absence of records that were never meant to be there. DMARC is the exception – its tree walk (RFC 9989 §4.10) would find the inherited policy either way.
How to fix itNothing to fix – this is a transparency note about which domain the e-mail results refer to.

SPF record present #

SPF (Sender Policy Framework) defines which servers may send on behalf of the domain and is one of the foundations against email spoofing.

Ratingpass as soon as a v=spf1 record is found (weight 2, or weight 1 under the hostname-without-MX exception below, which lowers the weight whether or not a record exists); whether it is the ONLY one is graded separately by Exactly one SPF record, and with several records the value line shows the first. fail when none is found. A TXT record that starts with v=spf1 but is not terminated by a space – v=spf1-all – is not an SPF record at all (RFC 7208 §4.5); the finding names that near miss instead of silently reporting "no SPF". A failed DNS lookup is reported as not testable and drops out of the rating rather than claiming the record is missing. When the record exists but does not parse, this row stays pass – a record IS published – and says so: receivers return PermError (RFC 7208 §4.6). A PermError is not the same as having no SPF: with no record a receiver simply has no information (§8.1), while §8.7 provides for the message to be rejected in the SMTP transaction for it, with reply code 550 (see SPF syntax valid). Exception: a hostname without MX whose organizational domain is a different name gets only a warn (weight 1) – the sending policy lives at that organizational domain; v=spf1 -all on the host is optional HELO hardening. The organizational domain comes from the Public Suffix List, and when it cannot be determined the exception does not apply. The drill-down lists every term of the record with its meaning in plain language, and it colours three kinds of term: the bare all – green for -all and ~all, amber for ?all, red for +all and for all without a qualifier – and, in amber, a mechanism standing after all and a redirect= that an all mechanism disables. The all colours follow the rows that grade those separately, so this table cannot contradict them. A MECHANISM standing after all is not described as authorising anything; it is marked as never evaluated, because RFC 7208 §5.1 requires receivers to ignore mechanisms listed after it. A modifier is a different case and is judged separately (§4.6.1 makes a term either a mechanism or a modifier): a redirect= is marked the same way but for another reason — §5.1 has receivers ignore it whenever the record contains an all mechanism, "regardless of the relative ordering of the terms", so moving it would not help — while an exp= is still evaluated there and is described normally, because §6.2 computes the explanation string exactly when a mechanism such as -all produced the fail, and §6 asks modifiers to appear "after all mechanisms".
How to fix itPublish an SPF TXT record that lists all legitimate sending hosts, e.g. v=spf1 include:… ~all (softfail, recommended with DMARC) or -all.

SPF syntax valid #

A syntax error anywhere in the record kills the whole record, not just the broken term. RFC 7208 §4.6 is explicit: "The syntax of the record is validated first, and if there are any syntax errors anywhere in the record, check_host() returns immediately with the result 'permerror', without further interpretation or evaluation." Every mechanism you carefully listed is then ignored, and a closing -all protects nothing – the record looks configured and authorises nobody. Flagged here: an impossible address literal (ip4:999.1.1.1), a prefix length outside 0–32 for IPv4 or 0–128 for IPv6 (§5.6) or written with a leading zero, a term that is neither a known mechanism nor a modifier, an all carrying an argument, a second redirect=, a CIDR length on a mechanism that takes none, and further malformed terms such as an empty redirect=, a second exp= or a macro that does not parse – the list is not closed.

Ratingfail with weight 2, no rating cap of its own. The row appears only when an SPF record exists and the whole-record check finds an error; a domain with no SPF record is covered by SPF record present instead. The value carries the internal error code, and for some error classes the offending term with it; the detail says in words what is wrong. The check stops at the FIRST error, so a record can hold more than one. Only the scanned domain's own record is parsed – a broken record at an include: target shows up under SPF include/redirect targets valid. While this finding stands, the rows that describe the record's EFFECT step aside: default behaviour, DNS lookups and include targets become informational with weight 0, because a conforming receiver returns PermError "without further interpretation or evaluation" (RFC 7208 §4.6) — whatever the record says can have no effect. The catch-all row keeps its verdict, but says that the hole is not live yet.
How to fix itCorrect the named term and re-publish, then re-scan: the check stops at the first error, so a second one may surface. Until the record parses, this domain has no working SPF policy at all – receivers return PermError, and §8.7 provides for the message to be rejected in the SMTP transaction for that, which is worse than publishing nothing (§8.1).

No catch-all IP range #

A /0 prefix matches every address of its family: RFC 7208 §5.6 compares only the high-order bits named by the CIDR length, and a length of zero names none. With a pass qualifier – ip4:0.0.0.0/0, +ip6:::/0, or the same term with no qualifier at all, which defaults to + (§4.6.2) – such a term authorises every host on the internet using that address family to send as your domain. A closing -all cannot undo it for those senders: mechanisms are evaluated left to right and the first match ends the evaluation (§4.6.2), so senders on that address family never reach the all term — senders on the other family still do, because an ip4: term cannot contain an IPv6 address (§5.6). This is syntactically perfectly valid, so the syntax check above cannot see it, and it is easy to leave in place after a migration. A denying qualifier on the same range (-ip4:0.0.0.0/0) is not flagged; ~ and ? are not flagged either, but they make the all term unreachable in the same way, which the default-behaviour row points out.

Ratingfail with weight 2, and a rating cap of C – the same cap +all receives, because the two are the same policy written differently and grading them differently would reward obscurity. The value lists the offending terms. Two consequences to expect in the report: the SPF default behaviour row steps aside – informational, weight 0, no cap – because the all mechanism behind such a range is no longer reached for that range’s address family and the same mistake must not count twice; and if the record ALSO has a syntax error – or the domain publishes more than one v=spf1 record – the cap is withheld, because receivers then authorise nobody at all. The finding itself stays, and says that fixing only the syntax would switch the hole on.
How to fix itReplace the /0 range with the networks that actually send for you. If authorising everything really was the intent, v=spf1 +all states it honestly – but it switches SPF off as a protection and is graded exactly the same.

SPF default behavior (all) #

The final all mechanism determines how unauthorized senders are handled. +all is dangerous. For sending domains, Mailhardener recommends ~all (softfail) over -all because forwarding breaks SPF, and RFC 9989 §7.1 gives an independent reason to be careful with a hard fail: some receiver architectures might run SPF ahead of DMARC, so a message that would have passed DMARC through an aligned DKIM signature could be rejected early in the SMTP transaction – and a message rejected before the DATA phase never appears in your aggregate reports, because the From domain is never revealed. -all together with DMARC remains correct; the choice is a trade-off, not a rule. Parked domains are the clear case: there -all is right.

Ratingpass with -all, and with ~all on a domain that actually sends; warn with ~all on a parked/non-sending domain (there -all is the right choice), with ?all, and when no all mechanism is present at all. +all – including a bare all, whose qualifier defaults to + – is fail and caps the module grade at C. Weight 2. Two things the value line shows: when the record has no all term but a resolvable redirect=, the default comes from the redirect target's first all term and is marked "(via redirect=…)" (RFC 7208 §6.1); a domain with no MX, no permitted senders and no redirect= is marked "Parked" — a record that delegates its policy with redirect= is the opposite of parked, whether or not the target could be read. The redirect= chain is followed beyond the first level – up to ten hops, the same budget receivers have under the 10-lookup limit of §4.6.4 – because §6.1 notes that "the newly queried domain can itself specify redirect processing". The row steps aside – informational, weight 0, no cap – in five cases where the all term is not what receivers apply: behind a catch-all range with a pass qualifier (graded by No catch-all IP range), while the record has a syntax error (graded by SPF syntax valid), when more than one v=spf1 record is published – §4.5 makes the record SELECTION fail, so receivers evaluate none of them (graded by Exactly one SPF record) – when a redirect= target publishes no SPF record, which §6.1 turns into a PermError rather than "none" – so the result is not neutral either, and when the redirect= chain loops back on itself, which receivers answer with a PermError once the 10-lookup limit of §4.6.4 is exceeded. It becomes a skip when this scanner could not read the redirect target at all: the default behaviour is defined there, so it was not measured. A ~/? range on /0 does not change the grade but is named: it, too, ends the evaluation before the all term for that address family. Without any SPF record this row is fail with weight 2; on a hostname without MX it is omitted entirely.
How to fix itSending domain: ~all or -all (with DMARC). Parked domain: -all. Never +all.

Exactly one SPF record #

Multiple v=spf1 records are forbidden (RFC 7208 §3.2) and make check_host() return a PermError (§4.5) – SPF then fails completely. A PermError is not the same as having no SPF at all: with no record a receiver simply has no information (§8.1), while a PermError signals a broken configuration and §8.7 provides for the message to be rejected in the SMTP transaction for it, with reply code 550.

Ratingfail + Cap C with more than one SPF record (PermError – no effective SPF policy). Weight 2.
How to fix itMerge all SPF records into a single one.

Deprecated ptr mechanism #

The ptr mechanism is slow, unreliable, and explicitly discouraged by RFC 7208.

Ratingwarn when the record uses the ptr mechanism OR the %{p} macro. RFC 7208 §4.6.4 puts the two on the same footing ("When evaluating the 'ptr' mechanism or the %{p} macro"), and §5.5 calls the mechanism deprecated; the row names which of the two it found. Weight 1.
How to fix itRemove the ptr mechanism from the SPF record.

Obsolete SPF record type (99) #

SPF once had its own DNS record type, SPF (type 99), next to the TXT form. RFC 7208 §3.1 ended that: SPF records "MUST be published as a DNS TXT (type 16) Resource Record (RR) only", and §14.1 says of type 99 that "its use is no longer appropriate for SPF version 1; implementations are not to use it". A type-99 record left over from that era is never evaluated by receivers – and once it drifts from the TXT record it documents a policy nobody enforces, which misleads whoever reads the zone. RFC 6686 Appendix A records how the dual-type experiment ended.

Ratingwarn (weight 1) when the domain publishes one or more records of type 99, whatever their content; the row says whether the content matches the TXT policy, differs from it, or stands alone without a v=spf1 TXT record. Not shown when no type-99 record exists; a failed lookup makes no claim.
How to fix itDelete the type-99 record(s) at the DNS provider and keep the policy in the TXT record only. If the type-99 record is the only SPF policy, publish it as TXT first.

SPF DNS lookups (limit 10) #

SPF allows a maximum of 10 terms that cause DNS queries. RFC 7208 §4.6.4 names them: the include, a, mx, ptr and exists mechanisms and the redirect modifier – all, ip4, ip6 and exp do not count. If more are needed, the result is a PermError and SPF is considered not passed. The same section carries further limits this finding does not count, among them a SHOULD of at most two "void lookups" (queries answering with no data or NXDOMAIN); that one is evaluated by the live sender test.

Ratingpass at 10 or fewer, fail above that (weight 1). The count comes from resolving the include/redirect chain recursively, counted per path – a target reached through two branches counts twice, which is why the number can differ from tools that count globally. Targets containing %-macros are counted but cannot be expanded and are not followed. Two states the number itself carries: a trailing "+" means the traversal was stopped at the limit, and "≥N" with a skip means it did not finish within the 15-second budget – then the true count is unknown and no confident pass is claimed. While the record has a syntax error the row is informational with weight 0: receivers stop before the first lookup anyway.
How to fix itReduce/consolidate includes. Avoid SPF flattening – it leads to outdated IPs.

SPF include/redirect targets valid #

An include: or redirect= pointing at a domain that publishes no SPF record causes a PermError per RFC 7208 – receivers then treat the entire SPF policy as broken. This typically happens when a former mail provider is cancelled but its include remains in the record.

Ratingfail when at least one include/redirect target verifiably publishes no SPF record (weight 2). The row is deliberately silent whenever no verdict is possible, so its absence is not proof that every target is fine: targets containing %-macros cannot be expanded without a live sender identity and are skipped; a target whose own lookup fails (SERVFAIL or timeout) is a temporary error, not a missing record, and no claim is made about it; and if the whole traversal runs into its 15-second bound, the finding is withheld entirely. When the traversal stopped at the lookup limit instead, the list is shown as a lower bound. While the record has a syntax error – or the domain publishes more than one v=spf1 record – the row is informational with weight 0: in both cases a conforming receiver returns PermError before any target is consulted (§4.6 for the syntax error, §4.5 for the record selection itself), so their state cannot change the outcome. With a syntax error it adds that it becomes a PermError of its own once the syntax is fixed.
How to fix itRemove stale includes from the SPF record, or restore the SPF record at the target domain.

DMARC record present #

DMARC ties SPF and DKIM to a policy and tells receiving servers what to do with spoofed mail – plus reporting. Without DMARC, spoofing protection is incomplete.

Ratingpass if a v=DMARC1 record exists under _dmarc; otherwise fail. Weight 3. More than one v=DMARC1 record at the scanned name is fail as well and caps the module grade at C: receivers discard all of them (RFC 9989 §4.10 step 2). Policy discovery continues up the tree from there, so an inherited record can still apply and the policy row can even be a pass – but nothing at the scanned name applies, and the cap stands. When the DNS lookup itself fails, the row reports "not testable" and drops out of the rating instead of claiming the record is missing. The drill-down lists every tag of the record that applies, with a plain-language meaning and a colour on the policy tags (green for reject, red for none, amber otherwise); a tag the standard does not define is shown as unknown and is simply ignored by receivers. When the scanned name has no record itself, receivers walk successively shorter names down to the TLD — the whole walk is capped at eight DNS queries, and that count includes the one at the scanned name, so at most seven shorter names are tried (DNS Tree Walk, RFC 9989 §4.10; psd= tags steer where the organizational domain sits and stop the walk) — and an inherited policy counts. The v value is case sensitive – a record such as v=dmarc1 is ignored entirely and is reported here as broken.
How to fix itPublish DMARC, starting with v=DMARC1; p=none; rua=mailto:…, then gradually tighten it.

DMARC policy (p=) #

p=none asks receivers to take no action against unauthenticated mail. RFC 9989 §3.2.12 calls that state "monitoring mode" only when the policy is p=none for the organizational domain and every subdomain below it AND the domain owner is receiving aggregate reports for it – without a rua, receivers MUST NOT generate those reports (§4.7), so such a record neither protects nor observes. Only p=quarantine (spam) or p=reject (rejection) reach enforcement (RFC 9989 §3.2.9: the Organizational Domain and all its subdomains are not none – an sp=none breaks it) and make spoofing actionable for receivers. Even p=reject is not a guarantee: receivers must not reject on that basis alone (§7.4), and DMARC does not cover forged display names (§11.4). A record that carries no p tag at all, or an invalid p/sp/np value, survives only as p=none if a valid rua URI exists – without one, receivers apply no DMARC at all. §4.10.1's condition is "does not contain a valid 'p' tag, or contains an 'sp' or 'np' tag that is not valid", which covers an absent p as well; §4.7 does read an absent p as an implicit p=none and points at §4.10.1 for the handling. This scanner follows §4.10.1.

RatingWhat is graded is the policy EFFECTIVE for the scanned name, not the p tag as written: on an inherited record that is sp when set, otherwise p, and the value line says which. A subdomain under an organizational domain publishing p=reject; sp=none is therefore graded as none. (On a record found at the scanned name itself, p is graded and a weak sp is flagged separately by Subdomain policy.) pass with reject; warn with quarantine; none (explicit, implicit, or as §4.10.1 fallback) → fail + Cap C; an inoperative record (no p tag or invalid values, and no rua) → fail + Cap C. Weight 3. When the effective policy is reject but the common-selector probe found no usable DKIM key, this finding carries an extra note – RFC 9989 §7.4 requires reject publishers not to rely on SPF alone.
How to fix itEscalate in stages per RFC 9989 §7.4: p=none with reports for at least a month, p=quarantine for an equally long period, then p=reject – and plan for more, because working through the reports can take many months (§5.1.7). Domains at p=reject must not rely on SPF alone – sign outgoing mail with DKIM (§7.4/§8); the same section is blunt about one case: it calls it "critical" that domains hosting users who might post to mailing lists SHOULD NOT publish p=reject, and asks those who do anyway to run the stages above first and to tell their users that list participation may be hindered.

DMARC test mode (t=) #

RFC 9989 introduced the t=y tag as the successor of pct=0: receivers apply the published policy one level lowerreject acts as quarantine, quarantine as none; reporting is unaffected and a policy of none does not change. Useful during rollout, but as long as it is set, p=reject does not fully protect against spoofing.

Ratingwarn (weight 2) when t=y weakens a quarantine/reject policy; info (weight 0) when the policy is none, where the tag has no effect.
How to fix itOnce the aggregate reports look clean, remove t=y so the policy takes full effect.

DMARC coverage (pct=) #

With pct<100, receivers on the older specification apply the policy to only a portion of the mail. The tag was removed in RFC 9989 (DMARCbis) and registered as historic (§9.3, defined there as "the tag is considered deprecated and is not expected to be in use in any current implementation"): receivers implementing the current specification ignore it entirely, while receivers still on RFC 7489 evaluate it. The tag therefore only creates a split between the two – one you can neither observe nor control.

RatingOnly evaluated when the tag is actually present, and graded by how far current and legacy receivers DIVERGE. pass at 100 % (weight 1), and warn for a syntactically invalid value (weight 1) – pct is not one of the tags RFC 9989 defines (§4.7 tag list, §4.8 Table 2) and is registered as historic (§9.3), so receivers on the current specification ignore it whatever it says; receivers still on RFC 7489 should discard a bad value in favour of that tag's default of 100 (RFC 7489 §6.3, a SHOULD). A broken value therefore has no effect that a missing one would not have. info with weight 0, outside the rating, when the effective policy is none: there is no enforcement to sample from, so the tag makes no difference under either specification. fail with weight 2 for pct=0 on an enforcing policy – the record reads as monitoring while receivers on RFC 9989 already apply p=quarantine/reject in full; deliberately no rating cap, because the policy itself is the one being applied. warn with weight 2 for a partial quota (1–99) on an enforcing policy, which splits your recipients into two behaviours. t=y is folded in first: it lowers the policy one level and can therefore turn an enforcing case into the weight-0 one. Graded is the record that APPLIES – on an inherited record, that of the organizational domain.
How to fix itRemove the tag (obsolete since RFC 9989) – but mind the order, because deleting it lifts receivers still on RFC 7489 to full enforcement: with a partial quota, first settle p/sp and align every legitimate sending path, then delete. A deliberate pct=0 should become t=y instead – RFC 9989 (App. A.6) introduces the t tag as the successor of exactly two pct values, t=y for pct=0 and t=n for pct=100; a partial quota has no successor tag.

DMARC aggregate reports (rua) #

Without a rua address, you receive no analysis of who is sending on behalf of the domain – the basis for safely moving to p=reject. Email is the only defined report transport: a rua list without any mailto: URI never delivers anything (RFC 9990 §3.5).

Ratingpass if rua is set and contains a mailto: address; warn when it is missing or mailto-less. Weight 1. Exception: on a no-mail domain (Null MX + SPF without permitted senders) a missing rua is only info (weight 0) – nobody can deliver legitimate mail there, reports would at most show spoofing attempts.
How to fix itAdd a rua=mailto:… address (e.g. a DMARC reporting service).

External report recipients authorized #

If rua/ruf points to a domain in a different organizational domain (determined via tree walk – sibling subdomains under the same organizational domain do not count as external), that destination must authorize acceptance via a <domain>._report._dmarc.<target> TXT record – otherwise receivers discard the reports. Failure reports (ruf) require the same authorization (RFC 9991 §5). What this check measures: RFC 9990 §4 states the rule for the host part of any report URI – its opening sentence speaks of “the host part of the authority component of a [RFC3986]”, and mailto appears in §4 only inside an example – so this row now resolves every URI in rua/ruf: the host of a mailto: address, or the authority host of any other scheme. At most the first eight distinct hosts among them are verified. Note that mailto: is the only transport a receiver must implement (RFC 9989 §4.7), and URIs with unsupported schemes are ignored – but the authorization rule itself does not distinguish. Externality is decided by a tree walk; if the scan's DNS budget for the walk is exhausted, it falls back to a plain suffix comparison, which can misjudge a sibling domain.

Ratingpass if every external rua/ruf destination is authorized; warn when the authorization record is missing, when the verification name exceeds DNS length limits (verification then can never succeed), or when the destination overrides rua/ruf with a foreign host (receivers then deliver to neither address). DNS errors during verification are reported as not testable, not as missing. Weight 1.
How to fix itPublish a TXT record <domain>._report._dmarc.<target> with v=DMARC1 at the target domain.

Subdomain policy (sp=/np=) #

A weak sp (e.g. none) undermines a strong p: subdomains can then still be spoofed. RFC 9989 additionally defines np= for non-existent subdomains – valuable because spoofers like to invent hosts such as invoice.example.com that have no DNS records at all. Both tags only work in the record at the organizational domain: policy discovery never reads them from records published further down – RFC 9989 §4.7 says so for sp; for np it follows from the policy discovery in §4.10.1.

Ratingwarn if p is strict but sp=none or np=none; otherwise info. Weight 0–1. sp/np in a record below the organizational domain are flagged as ineffective (info).
How to fix itSet sp/np to at least quarantine/reject, or remove them – but mind the order they inherit in: with sp absent, sp follows p; with np absent, np follows sp when an sp is present and only otherwise p (RFC 9989 §4.7). Removing np while sp=none stays therefore leaves non-existent subdomains unprotected. Publish both in the organizational domain’s record.

DMARC record syntax #

Beyond the policy itself, RFC 9989 tightened the record format: a syntax error MUST now be discarded in favour of the default value if there is one, or ignored outright (§4.8; RFC 7489 §6.3 only said SHOULD). Unchanged since RFC 7489, but worth stating: duplicate tags invalidate the record's whole tag-list – DMARC uses the DKIM tag-value syntax (RFC 9989 §4.7) and RFC 6376 §3.2 says "if a tag name does occur more than once, the entire tag-list is invalid", unconditionally; what receivers actually do with a duplicate varies, because §4.8 pulls the other way for errors inside a record, and that alone is reason enough not to publish one – fo= has hard constraints (0 and 1 are mutually exclusive, d/s at most once, ignored entirely without ruf), invalid tag values silently fall back to their defaults, and the tags ri=/rf= – like pct= – are listed as "historic" in the DMARC tag registry (§9.3), while the !size URI suffix survives in the grammar only as obsolete syntax that reporters are told to ignore (§4.8).

RatingOnly appears when something is off. warn – and then weight 1 – for duplicate tags, for broken fo= values, and for a psd=y on a name that is not on the Public Suffix List: receivers that honour the tag stop the tree walk there (RFC 9989 §4.10, steps 2/6), and for mail from every subdomain below it – where the record is no longer the one the walk started at – rule 2 of §4.10.2 moves the organizational domain to the name one label below this one, so alignment and the subdomain policy change for the whole tree. Declaring the name itself as the organizational domain is what psd=n does (§4.7, §4.10.2 rule 1). info with weight 0 for everything receivers simply ignore: tags removed from the standard (ri, rf, the !size URI suffix), invalid values that silently fall back to their default (t, psd, adkim, aspf), an fo= without a ruf to act on, a ruf with no mailto: URI, and a ruf on a psd=y record, which report generators must not honour without a specific agreement between the parties involved (RFC 9991 §2).
How to fix itClean up the record: remove obsolete (ri, rf, pct, !size) and duplicate tags, fix invalid values.

DKIM key discoverable #

DKIM cryptographically signs outgoing mail so that recipients can verify authenticity and integrity. Common selectors are checked.

Ratingpass when at least one of the probed common selectors publishes a USABLE key. Four published states do not count as a usable key: a p= that is EMPTY (revoked, RFC 6376 §3.6.1), a record with NO p= tag at all (§3.6.1 makes it REQUIRED, and §6.1.2 step 5 has verifiers reject such a record as a key syntax error), a record that publishes any tag MORE THAN ONCE (§3.2: "if a tag name does occur more than once, the entire tag-list is invalid" – reading it last-wins let the ORDER of the duplicates decide the verdict), and an s= service-type list naming neither email nor * once unrecognized types are dropped (§3.6.1: "Verifiers for a given service type MUST ignore this record if the appropriate type is not listed" — the everyday trigger is the typo s=mail). For the revoked case: if every selector found is revoked, the row is warn and says so – no signature of this domain can currently be validated. warn when no selector matched at all (the selector may differ; selectors cannot be enumerated in DNS, so absence here is a hint, not proof – the drill-down names every selector that was probed, with or without a hit). skip when no selector matched but at least one probe failed to resolve: the real selector could be exactly one of the failed probes, so no absence is claimed. Weight 2. On a hostname without MX, absent selectors are skipped (not graded) – the mail identity lives at the organizational domain. Likewise skipped on a no-mail domain (Null MX plus the empty SPF policy v=spf1 -all, the explicit no-mail declaration per RFC 7505 §4.2): a domain that never sends needs no signing keys. A softfail (~all), an SPF with permitted senders or a record with a syntax error keeps the regular check – a PermError declares nothing, and only a deliberate declaration of "this domain sends no mail" earns the exception.
How to fix itEnable DKIM at the mail provider and add the published selector in DNS. On a no-mail domain there is nothing to fix. If the domain used to sign mail, its former selectors can be revoked with a wildcard record *._domainkeyv=DKIM1; p= – an empty p= means the key has been revoked (RFC 6376 §3.6.1). Note that a wildcard only answers for names that have no record of their own, and it cannot reach anything already cached; explicitly published selector records take precedence and should be deleted. The M3AAWG parked-domains BCP, linked below, takes the other route for a parked domain and prefers publishing no DKIM record at all, since a missing public key already makes every forged signature fail.

DKIM key length #

A DKIM key that is too short does not merely weaken the signature – RFC 8301 §3.2 requires receivers to reject it: "Verifiers MUST NOT consider signatures using RSA keys of less than 1024 bits as valid signatures." Signers MUST use at least 1024 bits and SHOULD use at least 2048; verifiers only have to handle keys up to 4096 bits, so longer is not automatically more interoperable. Ed25519 (RFC 8463) is the modern alternative, but it is an addition, not a swap: a selector holds one key, so keep the RSA selector and publish Ed25519 under a second one until every receiver you care about verifies it.

RatingJudged per algorithm, so an Ed25519 key (256 bits) next to RSA-2048 does not trigger the weak-RSA verdict. The branches are checked in this order, and the first one that applies decides: fail when an RSA key is shorter than 1024 bits – the comparison uses the SHORTEST RSA key found, so one weak selector fails the row next to a strong one; warn when a published selector carries a p= value that cannot be read as a public key at all – mail signed with it fails verification wherever the key cannot be parsed, and the fix is to re-publish the value, not to lengthen the key (RFC 6376 §3.6.1 names PKCS#1 RSAPublicKey; errata 3017, Held for Document Update, adds that it MAY be wrapped in a SubjectPublicKeyInfo, and errata 7001, Reported, asks for SPKI outright, noting that the RFC format "is not what is used either in the wild, or in the examples in this RFC" — SPKI is the form this check reads); warn for any RSA key below 2048 bits — RFC 8301 §3.2 makes 1024 the hard minimum ("Signers MUST use RSA keys of at least 1024 bits") and 2048 the recommendation ("SHOULD use RSA keys of at least 2048 bits"), so 1536 bits meets the minimum and misses the recommendation exactly as 1024 does; the branch used to test for exactly 1024 and let everything between 1025 and 2047 pass; info when an RSA key is longer than 4096 bits — §3.2 obliges verifiers to handle 1024 to 4096 bits and leaves anything larger optional ("MAY"), so an oversized key is an interoperability risk rather than a defect; warn when a selector publishes a key type the IANA "DKIM Key Type" registry does not list – it holds exactly two entries today, rsa (registered by RFC 6376 §7.6, for which the registry now cites RFC 8017) and ed25519 (added by RFC 8463 §8.1) – AND its p= is neither an RSA nor an Ed25519 key: §3.6.1 has verifiers ignore an unrecognized type, so the rsa default applies, the material does not fit it, and §6.1.2 step 8 then fails every signature from that selector; info when every selector found is revoked (there is no key length to judge, and that is the domain's own declaration rather than a gap in our measurement); info, outside the rating, when no key length could be determined for any selector – that one IS a gap in our measurement, not a statement about your key; otherwise pass. Weight 1. Revoked selectors (empty p=) do not decide the status – they are graded by DKIM key discoverable – but they still appear in the value.
How to fix itRenew the DKIM key to RSA-2048 (or Ed25519). On Microsoft 365 / Exchange Online DKIM keys are still created with 1024 bits by default, so existing selectors (selector1/selector2) usually have to be upgraded manually: in Exchange Online PowerShell run Rotate-DkimSigningConfig -Identity yourdomain.com -KeySize 2048 (the -Identity is the domain name, not a GUID). The new key only starts signing after ~4 days (96 h) – until then the old 1024-bit key keeps signing – so re-test after that. Expect two rotations, not one: per Microsoft, changing the bit depth applies only to the next active selector during the first rotation, and the other selector reaches 2048 bits on the following one; a rotation already in progress blocks the next. Because this check grades the shortest RSA key it finds, the warning stands until no key is at 1024 bits any more. Verify with Get-DkimSigningConfig -Identity yourdomain.com | Format-List Selector1KeySize,Selector2KeySize,RotateOnDate. The Defender portal's Rotate DKIM keys button keeps the current bit depth, so the upgrade to 2048-bit needs PowerShell.

DKIM key attributes #

t=y marks a test key: RFC 6376 §3.6.1 – "Verifiers MUST NOT treat messages from Signers in testing mode differently from unsigned email, even should the signature fail to verify." The signature therefore does not protect the domain against abuse. The h= tag is a list of acceptable hash algorithms, and an absent h= allows all of them; only a list that names sha1 and no current algorithm pins the key to a hash RFC 8301 §3.1 withdrew ("rsa-sha1 MUST NOT be used for signing or verifying", and IANA lists sha1 as historic). h=sha1:sha256 still permits sha256 and is not flagged.

Ratingwarn (weight 1), and the row appears only when one of three applies: a t=y test flag, an h= list that permits sha1 and nothing current, or an h= list naming no usable hash at all (the two h= states are disjoint — a list of unrecognised names is the second, never the first). It stays a warn rather than a fail because the record alone does not prove the selector is in use: this check reads the KEY record, and a selector found by probing a candidate list may be stale or unused. Where it IS in use the consequence is not in doubt — RFC 6376 §6.1.2 step 6 has a verifier return PERMFAIL for any signature whose hash the h= tag does not list. The value names the affected selectors.
How to fix itRemove the test flag t=y; drop the h= restriction or include sha256 in it, and sign with a=rsa-sha256.

BIMI (brand logo) #

BIMI displays the brand logo (and, with VMC, a verified badge) in the inbox – a trust signal that only works with an enforced DMARC policy: p=quarantine/reject and sp not none, per the BIMI draft (revision 14) §7.1, and – following the BIMI Group's implementation guide – no pct below 100. With p=none, the logo is never displayed. This check additionally treats t=y as blocking: RFC 9989 §4.7 has the domain owner REQUEST that receivers apply the policy one level below the published one, so quarantine with t=y is expected to be applied as none, and the draft forbids BIMI at none. The draft is written against RFC 7489 throughout and never mentions that tag – treating it as a blocker is our reading, not a requirement of the specification. When a record with a logo exists, the check validates it the way a mailbox provider does: the SVG is fetched (SVG Tiny PS profile, no active content), the mark certificate (a=) is verified – BIMI marking, validity window, and a certificate chain ending at one of the Mark Verifying Authorities the BIMI Group lists (DigiCert, GlobalSign, SSL.com) – nobody certifies an MVA centrally, the BIMI Group states that it "does not certify an MVA to issue certificates for BIMI" and leaves acceptance to each mailbox provider, so this is measured against the published list they work from – and the served logo must match the image the CA bound into the certificate (RFC 9399 logotype, formerly RFC 3709). Via avp=personal, a record can also ask providers to prefer the sender's personal avatar over the logo – the check shows this preference. Only shown when a BIMI record exists.

Ratingfail when the record cannot display anything at all, whatever else is right: more than one v=BIMI1 record at default._bimi, a record with no l= tag, an l= URL that is not https (the draft's only supported transport for the logo), or an empty l= published next to a mark certificate – that is not an opt-out but a broken record, because indicator discovery then fails (§7.3), or a version tag that does not read exactly v=BIMI1, which §4.3 tells receivers not to correct. info for a deliberate declination, meaning an empty l= with a= empty or absent (§4.3.1/§7.5): an explicit opt-out is a valid configuration, and it is never downgraded for DMARC. skip when the DNS lookup itself failed. warn for a record that COULD work but does not verify: logo not fetchable, SVG carrying active content, logo not in the SVG Tiny PS profile, no mark certificate (a=), a= not https or not fetchable, certificate unreadable/expired/not chaining to a listed Mark Verifying Authority/lacking the BIMI marking, a served logo that differs from the image bound into the certificate, or an invalid avp= value: the draft has receivers ignore the value itself, but §4.3 also lets a mailbox provider treat an invalid preference as a failing record, so the logo may not appear at all. info when the logo and certificate could not be fetched during the scan: ten of the fourteen checks below depend on that request, so the row says only the record itself was examined — a limit of that run, not a finding about the configuration. good only when everything above verifies. When BIMI is set but DMARC is not enforced, that statement is APPENDED to whatever this row already found – it never replaces the row's own diagnosis – and it does NOT raise the weight: the fact it reports (DMARC is not enforced) is already graded by the DMARC policy row with weight 3, and charging it a second time meant a domain with a flawless BIMI record and a valid mark certificate scored worse than an otherwise identical domain publishing no BIMI at all. Every outcome carries weight 0: a logo is cosmetic, not security, so this row shapes the text of the report rather than the module grade. The drill-down names the record that answered – with a note when it was inherited from the organizational domain instead of published by the queried name – and the result of the logo check. The mark certificate check and the logo-to-certificate binding appear only for a record carrying an a= tag: without one there is no certificate to fetch, and two empty rows would suggest a check that never took place.
How to fix itPublish a BIMI record default._bimi with an SVG Tiny PS logo (l=) and a VMC/CMC from a Mark Verifying Authority listed by the BIMI Group (a=); serve exactly the certified logo file, and first bring DMARC to quarantine/reject, without t=y and without sp=none; remove any remaining pct tag. On pct the sources disagree: the BIMI draft (revision 14) requires pct=100 only under p=quarantine and accepts p=reject "with any percentage value", while the BIMI Group implementation guide does not accept a pct below 100 at all ("‘None’ policies or ‘pct’ less than 100 percent are not accepted"). This check follows the stricter requirement. Removing the tag is the fix, in this order: receivers on the current specification already ignore it, but receivers still on RFC 7489 apply the policy to only part of the mail, so align every sending path before deleting it.

BIMI logo over IPv4/IPv6 #

The BIMI draft allows exactly one transport for the logo named in l=: "The only supported transport is HTTPS" (§4.3). Every mailbox provider that shows your logo fetches that file itself. This scan therefore fetches it twice – once over IPv4 and once over IPv6, each request pinned to one address – instead of letting the operating system pick a family, because a single fetch would report "works" for a host that is broken over exactly one family and would hide the defect it exists to find. A logo served only over IPv6 verifies perfectly and still never appears at any IPv4-only provider, and no DNS-level check can show that. This row is about reaching the logo file; the record itself and the mark certificate are judged by BIMI above.

RatingAppears only when the l= URL is https and there is actually something to say. warn when the logo host publishes only an AAAA record; warn when it publishes both families but one does not deliver the file – the row distinguishes a family that answered with a status other than 2xx from one that did not answer at all; warn when both families deliver different content, which cannot be detected once a redirect was followed. skip ("not testable") when this scanner itself has no route for a family, or when its HTTP client discarded the answer as malformed – neither is a statement about your host. Weight 0 in every case, for the same reason the BIMI finding itself is weight 0: a logo is cosmetic, not security. Deliberately not the MTA-STS twin's reason — that row steps aside because its parent grades the same measurement, whereas the BIMI finding never measures the address family at all, so this row is the only place an IPv6-only logo host is seen. Nothing is shown for a working dual-stack host, for the ordinary IPv4-only case, or when no address could be probed at all – which also covers the case where our own address lookup failed. Only the first public address of each family is fetched.
How to fix itPublish an A record next to the AAAA record for the logo host, or serve the logo from a dual-stack host; if the two families answer differently, one origin is out of date – the file must be byte-identical, because the mark certificate binds a hash of exactly one image. Nothing to fix when the row says "not testable".

Null MX (RFC 7505) #

An MX 0 . explicitly signals that the domain receives no email – correct for web-only/parked domains and prevents delivery attempts.

RatingThe row appears whenever the MX RRset names no usable mail host. pass when an MX 0 . is the ONLY record – that is the correct form. warn in three shapes, weight 1 each: an MX 0 . beside regular MX records (RFC 7505 §3: "A domain that advertises a null MX MUST NOT advertise any other MX RR"), an MX 0 . beside a second record that points at the DNS root – either a preference other than 0 or a duplicate MX 0 . – and such a malformed root target standing alone. What follows differs: only in the first shape does the domain still have a mail host, so MTA-STS, TLS-RPT and DKIM are graded as for a receiving domain; in the other two nothing can be delivered, MTA-STS and TLS-RPT are skipped without weight, and only DKIM stays graded.
How to fix itFor domains that neither send nor receive mail, set MX 0 . plus v=spf1 -all and DMARC p=reject. RFC 7505 §4.2 warns about the half-way case: a domain that still uses its name in RFC5321.MailFrom or RFC5322.From addresses should not publish a Null MX at all, because it risks having its mail rejected over the undeliverable return address – so if the domain still sends, this is not the configuration for it. On DKIM the sources differ: the M3AAWG BCP linked below recommends publishing no DKIM record for a parked domain, since a missing public key already makes every forged signature fail; if the domain once did sign mail, a wildcard revocation *._domainkeyv=DKIM1; p= states the revocation explicitly (RFC 6376 §3.6.1: an empty p= means the key has been revoked). A wildcard only answers for selector names that have no record of their own.

Mail provider #

The MX records reveal which platform receives the domain's mail. For a hosted mailbox provider (Google Workspace, Microsoft 365, Zoho …) the same platform usually sends the domain's mail too, so the recognised provider is a strong hint – but only a hint – for the outbound sender. Inbound security gateways (Mimecast, Proofpoint …) sit in front of the mailbox; their MX entry does not establish who sends the domain’s mail — many of these providers relay outbound as well, while other customers use them inbound only. A gateway is therefore not treated as the sender.

RatingInformational only – status info with weight 0, so it never affects the grade and never caps it. The provider is recognised from the MX host names against a maintained list; the mapping is ours, no standard defines it. Four kinds of platform are distinguished, and when several match, the strongest signal for the sender wins: a hosted mailbox provider or freemail provider usually sends the domain's mail too; an email sending service (ESP) appears in MX when a dedicated sending or newsletter (sub)domain routes bounces and replies there, and is then a sender for that name; an inbound security gateway sits in front of the mailbox and says nothing about the sender. For the kinds that are themselves senders, the finding compares the provider's own documented outbound include: against the published SPF record and suggests adding it when it is missing. Providers that offer interchangeable regional includes are satisfied by any one of them. The two sources below state in their own labels which part of this they cover; the provider list itself carries none, because there is no standard to point at.
How to fix itIf the recognised provider sends mail for the domain, check that its documented SPF include: is in the v=spf1 record and add it otherwise – where the provider offers interchangeable regional includes, one of them is enough. Other senders (newsletters, CRM, transactional/ESP) are not visible in MX and must be added to SPF separately.

MTA-STS #

MTA-STS enforces TLS for mail delivery and protects against downgrade/MITM attacks. The TXT record (incl. the mandatory id= field per RFC 8461 §3.1), policy file, mode, max_age – §3.2 sets one hard maximum of 31557600 seconds (about a year) and expects the value to "typically be in the range of weeks or greater", so anything under one week (604800 s) is treated as too short, which is this check's floor and not a number from the RFC – and whether the mx entries match the actual MX are checked. The field names and the version/mode values are compared case-sensitively, as §3.2 spells them.

RatingOnly graded when the domain has an MX: without one the row is skipped with weight 0, and an MX lookup that failed is reported as not testable rather than as "no MX". A failed _mta-sts lookup is likewise reported as not testable, not as a missing record. fail when more than one v=STSv1 TXT record is published – senders then behave as if no policy existed, and the policy itself is not examined further. Otherwise pass only for mode: enforce with a policy that carries a valid version: STSv1 line, an mx set covering every real MX host, a max_age written as plain digits, and a TXT record carrying the mandatory id=. Anything short of that is warn: mode: testing or mode: none, a policy with no mx line at all UNLESS the mode is none — the §3.2 ABNF requires mx "at least once, except when mode is 'none'", and §8.3 recommends exactly such a policy for opting out; for enforce and testing it matches none of your hosts, so senders honouring it cannot deliver — a missing mode line or a mode value outside testing/enforce/none, an mx set that misses a real MX, a missing or non-numeric max_age, a max_age above the allowed 31557600 (the only limit RFC 8461 §3.2 states) or below one week — 604800 seconds, a threshold of this scanner's, since §3.2 sets no minimum and only expects values "in the range of weeks or greater" —, a missing version: STSv1 line, a field name or a version/mode value in the wrong case (§3.2 defines them case-sensitively, so a strict sender reads them as unknown fields), an id= that is missing, spelled ID= or outside the 1*32(ALPHA / DIGIT) grammar RFC 8461 §3.1 defines for it — such a record is syntactically invalid, and §3.1 then has senders assume the domain has no available policy at all —, a policy file served with a media type other than text/plain (§3.2 asks senders to validate it; parameters such as charset are ignored, and a missing header says nothing), an mx line that is not a ["*."] Domain and therefore matches nothing, a policy host reachable over IPv6 only, and a policy file that cannot be fetched. fail, additionally, when the mx set covers NONE of the real MX hosts while the mode is enforce: §5 tells a sending MTA it "MUST NOT deliver the message to hosts that fail MX matching" and §5.1 step 2 moves to the next candidate only "if there is one", so nothing is left to deliver to — that caps the module at C. Partial coverage stays warn, because the covered hosts still receive mail. skip with weight 0 when the policy file is larger than the 64 KB this scanner downloads: a cut-off body would turn our own limit into missing mx or max_age lines and report them as your configuration. An unreachable policy names its cause: a host resolving only to private/internal addresses, no A/AAAA records at all, an HTTP status other than 200, a redirect (RFC 8461 §3.3 allows only 200 and forbids redirects), a timeout, or a host mixing private and public addresses in public DNS. Missing → warn. Weight 2.
How to fix itTXT _mta-sts + a policy at https://mta-sts.<domain>/.well-known/mta-sts.txt with mode: enforce, correct mx lines, and max_age ≥ 604800.

MTA-STS policy over IPv4/IPv6 #

A sending MTA can only apply the policy it can actually fetch, and RFC 8461 §3.3 says what happens when it cannot: "If a valid TXT record is found but no policy can be fetched via HTTPS (for any reason), and there is no valid (non-expired) previously cached policy, senders MUST continue with delivery as though the domain has not implemented MTA-STS." A policy host reachable over only one address family therefore does not weaken MTA-STS for the senders on the other family – it switches it off for them, silently, while the TXT record still advertises it. This scan fetches the policy over each published family separately, each request pinned to one address, instead of letting the operating system choose: a single fetch would report "works" for exactly the host that is half broken, hiding the defect the check exists to find.

RatingAppears only when a _mta-sts TXT record exists and there is something to report. warn when the policy host publishes only an AAAA record; warn when both families are published but one does not deliver the policy – the row distinguishes a family that answered with a status other than 2xx from one that did not answer at all; warn when both families answer with different policy text, because which family a sender picks is not something you control. skip ("not testable") when this scanner has no route for a family, or when the family answered and this scanner's HTTP client discarded the response as malformed: that is our limitation and never a statement about your host. Weight 1 for the warn cases – half the weight of the MTA-STS finding itself, because a missing family narrows who can apply the policy rather than removing it – and weight 0 for skip. Weight 0 as well when the MTA-STS row above already downgraded the same IPv6-only host: one fact is charged once, and this row stays visible as the explanation. Nothing is shown at all for a working dual-stack host, for the ordinary IPv4-only case, or when no address could be probed – which also covers our own lookup failing. Only the first public address of each family is fetched.
How to fix itPublish an A record for mta-sts.<domain> alongside its AAAA record, or serve the policy from a dual-stack host – and make sure both origins return byte-identical policy text. If the row says "not testable", there is nothing to fix on your side.

TLS-RPT (SMTP TLS Reporting) #

TLS-RPT provides daily reports about TLS delivery attempts, successful and failed – important for noticing MTA-STS/DANE problems before they cost you mail.

RatingGraded whenever mail is delivered to this domain – which is not the same as having an MX record. With an empty MX RRset and an address record, RFC 5321 §5.1 treats the domain itself as an implicit mail exchanger, and RFC 8460 ties the reporting policy to no MX at all, so the row is graded there too; the main scan probes that same host elsewhere in the same report — unless every one of its addresses lies in a CDN edge network, in which case no connection is attempted on port 25 at all and the "Mail server present" finding says so. It is skipped without weight only when nothing can be delivered (no MX and no address record, or an MX RRset naming no mail host), and reported as not testable when a lookup failed. Then: pass when a v=TLSRPTv1 record exists AND its rua= names a usable destination – RFC 8460 §3 supports exactly two URI schemes, mailto: and https:, the rua field is required, and a mailto: needs a complete address. fail when the record exists but carries a rua= that cannot be used — it looks configured and reports nothing, which is worse than publishing none; warn when the record exists with no rua= at all (RFC 8460 §3 makes the field mandatory, and the Record Studio rates that case fail — the scan is the milder of the two surfaces here), and warn when no record exists. A rua= list with one usable and one broken destination stays pass – reports do arrive – but the broken entry is named: it is the address the operator entered because he wanted reports there. warn when the version tag is not spelled exactly v=TLSRPTv1 (§3 requires the value to be exactly TLSRPTv1 and defines it in ABNF as a case-sensitive literal (%s"v=TLSRPTv1", RFC 7405), so the record does not parse as a TLS-RPT policy and a sender following §3 finds no reporting policy for the domain — the same outcome as publishing none, hence the same rating, but the row names the cause instead of saying "not present"). fail when more than one v=TLSRPTv1 record is published: §3 tells senders to "assume the recipient domain does not implement TLSRPT", so no reports are delivered at all. Weight 1.
How to fix itPublish a TXT record _smtp._tls with v=TLSRPTv1; rua=mailto:… (an https:// endpoint works too, RFC 8460 §3). For the mailto route, note that §3 requires reports sent via SMTP to carry a valid DKIM signature of the reporting domain – reports without one must be ignored by the recipient.

← All checks & scoring methodology