DMARC record explained with a real site: p=reject from the first week, and the pct tag the new standard removed

By Jhon Patzi · Published · 34 min read

Two halves of a split wooden block floating side by side in the dark, their broken edges matching - dmarc record

The short version. A DMARC record is a TXT record at _dmarc under your domain that tells receiving servers what to do with mail whose From domain is not backed by an aligned SPF or DKIM pass, and where to send reports about it. This site’s DMARC record has three tags, v=DMARC1; p=reject; rua=…, and it has said p=reject since the site’s first week, against the start-at-none advice in Google’s and Microsoft’s setup guides. The one failure on record cost five of the server’s own messages. Since May 2026 the standard is RFC 9989, which removed the pct tag that Google’s guide still uses to ramp up, and asks receivers not to reject on p=reject alone. Gmail, Microsoft 365 and Yahoo all document rejecting mail that fails it.

This site’s DMARC record with its three published tags, and the defaults a receiver fills in for the tags it leaves out
Three tags are in DNS, and the standard supplies the rest. Here every default points the same way: relaxed alignment, and reject for the domain and all of its subdomains.

A DMARC record can be explained as a list of tags and their allowed values, and a generator will write one for you. The list is the easy part. What a generator cannot tell you is what your record asks of the servers that receive your mail, which of your messages pass because of it and which would not, and whether the advice it was built on still matches the standard. Those questions have answers you can measure, so this note measures them on one real domain.

The domain is this one, visibleorigin.com, and this note is the companion of this site’s SPF record, explained entry by entry. That note was about the envelope of a message. This one is about the From line people actually read, and about the policy that ties the two together. Everything here was measured on October 22, 2026, against public resolvers and on the server behind the site, unless another date is given, and the commands are included so you can run them against your own domain. One value is not shown as published: the address in the rua tag, where reports are sent, is replaced here by dmarc-reports@example.com. The real address is not published in this note.

What a DMARC record is, according to RFC 9989

DMARC stands for Domain-based Message Authentication, Reporting, and Conformance, and since May 2026 its specification is RFC 9989. The record lives in DNS: “DMARC Policy Records are stored as DNS TXT records with names starting with the label ‘_dmarc’.” So the DMARC record for example.com is the TXT record at _dmarc.example.com, and a server that receives a message with example.com in its From line looks for it there.

What the record governs is narrower than its name suggests. DMARC looks at one domain, the one in the From header, which the standard calls the Author Domain, and asks whether SPF or DKIM authenticated a domain that matches it. The pass rule fits in one sentence: “If one or more of the Authenticated Identifiers align with the Author Domain, the message is considered to pass the DMARC mechanism check.” An authenticated identifier is either the domain that SPF checked in the envelope or the domain in the d= tag of a DKIM signature that verified. Aligned means it is the same domain as the From line or, in the default relaxed mode, shares its organizational domain, so a signature from mail.example.com aligns with a From line at example.com.

Alignment is the whole point, and RFC 9989 explains why with SPF: “any Domain Owner, even a bad actor, can publish an SPF record for its domain and send an email that will obtain an SPF ‘pass’ result.” A pass proves that some domain authorized the sending server. Only alignment proves the domain was yours. An SPF pass is not an aligned SPF pass, and a message can carry spf=pass in its headers and still fail DMARC, because the domain that passed belongs to the company that sent the message for you. The same is true of DKIM: a message can carry a perfectly valid signature from a domain that has nothing to do with its From line.

Two more things in the definition matter for everything below. The record states a preference, and the receiver decides what to do with it. RFC 7489, the previous version, called the p tag the “Requested Mail Receiver policy”; RFC 9989 calls it the “Domain Owner Assessment Policy”. And the record can also ask for reports, which is how a domain owner learns what receivers actually saw.

RFC 9989 is also a change of status. Its own appendix says RFC 7489 “was not the product of any IETF work stream but was instead published into the RFC Series by the Independent Submissions Editor and classified as an Informational RFC.” RFC 9989 is on the Standards Track and obsoletes both RFC 7489 and RFC 9091, the experimental extension for public suffix domains that introduced the np tag. The reports now have documents of their own: RFC 9990 for aggregate reports and RFC 9991 for failure reports. The documentation has not caught up. On October 22, Google’s DMARC setup page still linked to RFC 7489, Microsoft’s said DMARC records “are exhaustively described in RFC 7489”, and Yahoo’s FAQ said the specification “is published as RFC 7489”. That gap comes back below, in the advice about how to roll a policy out.

This site’s DMARC record, tag by tag

This is the whole record, fetched from a public resolver:

shell
$ dig +short TXT _dmarc.visibleorigin.com @1.1.1.1
"v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com"

The address after mailto: is the placeholder mentioned above; the rest is exactly as published. There is one record at that name, and that matters more than it looks. When a receiver finds more than one, RFC 9989 is blunt: “If multiple DMARC Policy Records are returned for a single target, they are all discarded.” Two records do not combine; they cancel, and the domain is treated as if it published no policy at all. Brevo’s help center warns about exactly this: “Having multiple DMARC records can interfere with domain authentication.”

The record is also signed. Asked with DNSSEC, the resolver sets the ad flag, which means it validated the answer, and returns a signature next to the record (output trimmed):

shell
$ dig +dnssec +noall +comments +answer TXT _dmarc.visibleorigin.com @1.1.1.1
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
_dmarc.visibleorigin.com. 300 IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com"
_dmarc.visibleorigin.com. 300 IN RRSIG TXT 13 3 300 20261023222846 20261021202846 34505 visibleorigin.com. ...

That is not decoration. RFC 9989 lists DNSSEC among the defenses against an attacker who can forge DNS answers and so “make aligned mail appear unaligned or vice versa”. The _dmarc label itself is one of the underscored names that RFC 8552 sets aside for attribute records, a convention I ran into from the other side when publishing a DNS-AID record for this site.

What each tag does, written or not

Three tags are written. A receiver fills in the rest from the defaults in RFC 9989, so the policy it applies is longer than the record in DNS. This is the full reading:

Tag, and its value hereWhat a receiver does with it
v=DMARC1, writtenIdentifies the record. It must be the first tag, and a record without it is ignored.
p=reject, writtenThe policy for mail that fails DMARC. The owner “considers all such failures to be a clear indication that the use of the domain name is not valid.”
rua=mailto:…, writtenWhere aggregate reports go. Without it, receivers “MUST NOT generate aggregate feedback reports for the domain.”
adkim and aspf, not written, so rRelaxed alignment for DKIM and for SPF: a subdomain of visibleorigin.com aligns with it.
sp, not writtenExisting subdomains get the policy in p, here reject.
np, not writtenSubdomains that do not exist get sp, or p when there is no sp: reject again.
t, not written, so nNot a test. The policy is meant to be applied as written.
ruf and fo, not writtenNo failure reports are requested, and fo means nothing without ruf.
psd, not written, so uNot a public suffix. The organizational domain is found with the DNS tree walk.

The record as RFC 9989 reads it, section 4.7. Only the first three rows are in DNS.

Two of those defaults decide more than the written tags do. The first is alignment. Both modes default to relaxed, and RFC 9989 is candid about why that is enough: “In practice, nearly all Domain Owners have found relaxed alignment sufficient to meet their needs.” Google’s Set up DMARC page agrees: “Relaxed alignment typically provides sufficient spoofing protection.” Then it uses adkim=s; aspf=s in the example record at the top of the same page. Copy that example and a provider that signs with a subdomain of yours stops aligning. So does one that puts a subdomain in the envelope.

The second is subdomains. With neither sp nor np in the record, p=reject covers every name under visibleorigin.com, including names that were never created. RFC 9989 spells out the fallback: “If the ‘np’ tag is absent, the policy specified by the ‘sp’ tag (if the ‘sp’ tag is present) or the policy specified by the ‘p’ tag (if the ‘sp’ tag is not present) MUST be applied for non-existent subdomains.” A forger is free to invent invoices.visibleorigin.com, and a policy that covered only the domain itself would say nothing about it.

The subdomain that does not exist, and the NXDOMAIN that never comes

Here the measurement turned up something the standard does not account for. RFC 9989 decides whether a subdomain exists the way RFC 8020 does: “if the response code received for a query for a domain name is NXDOMAIN, then the domain name and any possible subdomains do not exist.” I asked three public resolvers about invoices.visibleorigin.com, a name that does not exist, and none of them answered NXDOMAIN (output trimmed):

shell
$ dig +dnssec A invoices.visibleorigin.com @1.1.1.1
;; ->>HEADER<<- opcode: QUERY, status: NOERROR
invoices.visibleorigin.com. 1800 IN NSEC \000.invoices.visibleorigin.com. RRSIG NSEC TYPE128

The same NOERROR came back from 8.8.8.8 and 9.9.9.9, with and without DNSSEC, and from the zone’s own authoritative server. This is compact denial of existence, standardized in RFC 9824: instead of saying that a name does not exist, the server says that it exists and has no records of the type asked for, and marks the answer with a synthetic type called NXNAME, number 128. That is the TYPE128 at the end of the line. RFC 9824 is explicit about the consequence: “For DNSSEC-enabled queries, a signed zone at an authoritative server implementing Compact Answers will never return a response with a response code of NXDOMAIN, unless they have implemented the optional response code restoration feature described in Section 5.1.” Its notes on implementations name this site’s DNS provider: “Since September 2024, both Cloudflare and NS1 have deployed NXNAME using the officially allocated code point of 128.”

So on this zone, a receiver that waits for NXDOMAIN before applying np will not get it from these resolvers, and would treat invoices.visibleorigin.com as an existing subdomain. For this record that changes nothing, because without sp or np the answer is p=reject either way. It would matter for a domain on a zone like this one that set a stricter np than its sp, for example sp=none; np=reject to lock down invented names while its real subdomains are still being fixed. Those invented names would get sp=none.

Every path this domain’s mail takes, and what passes on each

A record only means something next to the mail it covers. So the next step is to list every way mail with visibleorigin.com in its From line leaves, or arrives, and ask for each one which identifier passes and whether it aligns. There are two ways out and one way in, plus a fourth path I have to include because it is the one that failed.

Two mail paths compared with the From line: the server aligns on DKIM and SPF, WordPress through Brevo on DKIM only
Both paths pass DMARC, but not on the same evidence. The Brevo path has one aligned identifier, so losing its DKIM selector would fail all of its mail.

The server itself: two aligned identifiers

Mail that Postfix sends directly as visibleorigin.com is signed by OpenDKIM with the selector default and d=visibleorigin.com, and its envelope carries the same domain, which the SPF record authorizes through the server’s IPv4 and IPv6 addresses. When test messages through both outbound paths were delivered to Gmail on October 4, the header Gmail added to this one read dkim=pass, spf=pass and dmarc=pass. Both identifiers align, so this path survives the loss of either one.

It is also the quietest path. Since September 20, the earliest date in the server’s mail log, OpenDKIM has signed exactly one message for this domain, the test of October 4, and the log has no line that mentions DMARC or a 5.7.26 rejection. The one thing on this path below current advice is the key. It is 1,024 bits, the minimum. RFC 8301 says “Signers MUST use RSA keys of at least 1024 bits for all keys. Signers SHOULD use RSA keys of at least 2048 bits.” Google’s Email sender guidelines put it in their own words: “Sending to personal Gmail accounts requires a DKIM key of 1024 bits or longer. For security reasons, we recommend using a 2048-bit key if your domain provider supports this.” The keys this domain publishes for Brevo and for Cloudflare are 2,048 bits. The size can be read straight from DNS:

shell
$ dig +short TXT default._domainkey.visibleorigin.com @1.1.1.1 | tr -d '" \n' \
    | sed -E 's/.*p=([^;]+).*/\1/' | base64 -d \
    | openssl pkey -pubin -inform DER -noout -text | head -1
Public-Key: (1024 bit)

WordPress through Brevo: one aligned identifier

Everything WordPress sends goes through Brevo’s relay, by way of the FluentSMTP plugin. Its log covers October 11 to 21 and holds 11 messages, all sent and none failed. Brevo signs that mail with this domain. Two selectors are published for it as CNAME records that point into Brevo, brevo1 and brevo2, both 2,048-bit keys. On October 4 the header Gmail added to a message sent this way read dkim=pass with the selector brevo2, and dmarc=pass.

The envelope is Brevo’s. Its domain setup guide says so directly: “Without a branded subdomain, SPF passes via Brevo’s infrastructure, not your own domain.” This site uses Brevo’s shared relays without a branded subdomain, so on these messages SPF passes for Brevo’s domain, and that pass does not align with visibleorigin.com. DMARC passes here on DKIM alone. I have not read the envelope of a Brevo message myself, so that half rests on Brevo’s documentation; what it means for the SPF record is in the SPF note.

The same Brevo page draws a conclusion that the standard does not: “Branding moves SPF alignment onto your sending domain, which means you can safely enforce a strict DMARC policy (p=quarantine or p=reject) and rely on two independent authentication signals instead of one.” The implication is that p=reject without SPF alignment, which is this site’s setup, is a risk. RFC 9989 names the opposite one. In its section on forwarders and mailing lists it says that “domains that publish ‘p=reject’ MUST NOT rely solely on SPF to secure a DMARC pass and MUST apply valid DKIM signatures to their messages,” because a forwarder usually breaks SPF, or rewrites the envelope away from your domain, while “DKIM signatures will generally remain valid in these relay situations.” DKIM is the identifier the standard requires at p=reject, and it is the one this path has.

The risk Brevo points at is real, though, and it has nothing to do with forwarding. With one aligned identifier, everything depends on the two CNAME records staying in place and on Brevo signing with them. If the selector Brevo signs with were deleted, every message from WordPress would fail DMARC, and the receivers that honor p=reject would refuse it. Nothing else would hold it up. That is the case for a second aligned identifier on this path, and it is why the selectors are part of every review of a domain’s email routing and deliverability I do: on a DKIM-only path, whether the key published in DNS is the key actually in use decides everything.

Mail sent to this domain: the policy enforced at the door

The third path runs the other way. Mail addressed to this domain arrives at Cloudflare Email Routing, which the domain’s MX records point to, and is forwarded to a mailbox elsewhere. Two things happen there that involve DMARC. The first is that this site’s own policy is enforced on the way in. Cloudflare’s postmaster reference says: “For Email Routing, incoming emails are rejected if they fail authentication according to the sender’s DMARC policy.” A forged message that claims to come from visibleorigin.com and is addressed to visibleorigin.com has no aligned SPF or DKIM pass, so by that rule it is turned away before it is forwarded. I have not tested it, because testing it means sending a forged message.

The second is what forwarding does to everyone else’s mail. Cloudflare rewrites the envelope with the Sender Rewriting Scheme, and “The From: header of the message is not modified.” The rewritten envelope passes SPF for a Cloudflare domain, which cannot align with the original sender’s From line. Cloudflare also adds signatures of its own: “For forwarded emails, Email Routing adds two DKIM signatures: one for email.cloudflare.net, which covers sender rewriting, and one for the recipient domain configured by the customer.” The second is the cf2024-1 key published on this domain. Neither aligns with the original From line either. Whether a forwarded message passes DMARC at the final mailbox depends on the original sender’s DKIM signature surviving the trip. Cloudflare’s list of known limitations says as much: “Due to the nature of email forwarding, restrictive DMARC policies might make forwarded emails fail to be delivered.” Cloudflare also supports ARC, the protocol meant to carry the original results across a forwarder, but RFC 9989 is sober about that whole family of fixes: “as of this document’s publication, none of the methods have become widely used.”

The path that failed: the server before September 9

The fourth path is the server again, as it was in late August and early September. Its messages were signed with d=visibleorigin.com, but the public key had never been published, so no receiver could verify the signature, and the server was not yet in the SPF record, so SPF did not pass either. Neither identifier produced an aligned pass. By early September an aggregate report had shown five of those messages rejected. The DKIM half of that story, including how OpenDKIM’s own test tools misreported the key, is in the note on the two OpenDKIM tools that misreported my keys, and the SPF half is in the SPF note. From the DMARC side it is the simplest case in this note: no aligned identifier, a published p=reject, and a receiver that did what the record asked.

PathAligned identifiersDMARC
The server, since September 9SPF on visibleorigin.com, and DKIM with default, 1,024 bitspass, twice over
WordPress through BrevoDKIM with brevo2, 2,048 bits. SPF passes for Brevo’s domain, not alignedpass, on DKIM alone
The server, before September 9None: DKIM key not in DNS, server not in SPFfail: five rejected
Forged mail using this domainNonefail: rejection requested

First two rows: headers added by Gmail on October 4, 2026, with Brevo’s envelope taken from its documentation. Third row: an aggregate report of early September. Fourth row: what the record asks for, not a test.

What p=reject asks for, and what receivers say they do

The policy is the tag with consequences, so it is worth reading what the standard says it is. RFC 7489 described p=reject as a request: “The Domain Owner wishes for Mail Receivers to reject email that fails the DMARC mechanism check.” RFC 9989 describes it as an assessment: “The Domain Owner considers all such failures to be a clear indication that the use of the domain name is not valid.” What happens next belongs to the receiver: “Email messages that fail the DMARC mechanism check are handled in accordance with the Mail Receiver’s local policies.”

RFC 9989 then says it twice more, in stronger terms. Section 5.4: “Mail Receivers MAY choose to accept email that fails the DMARC validation check even if the published Domain Owner Assessment Policy is ‘reject’.” Section 7.4, about forwarders and mailing lists: “It is therefore critical that Mail Receivers MUST NOT reject incoming messages solely on the basis of a ‘p=reject’ policy by the sending domain.” It even names the fallback: “In the absence of other knowledge and analysis, Mail Receivers MUST treat such failing mail as if the policy were ‘p=quarantine’ rather than ‘p=reject’.” And the same section admits how far that is from practice: “In practice, despite this advice, few Mail Receivers apply any mitigation techniques when receiving indirect mail flows”.

The receivers whose documentation I read describe something much closer to the old wording:

ReceiverWhat its documentation says about mail that fails a p=reject policy
GmailRefused during the SMTP conversation with 550 5.7.26: “Unauthenticated email from domain-name is not accepted due to domain’s DMARC policy.”
Microsoft 365Set by each organization’s anti-phishing policy. Honoring the sender’s policy is on by default, and the default action for p=reject is “Reject the message”. An admin can choose quarantine instead, or stop honoring it.
Yahoo“any message without a proper DKIM signature or SPF alignment will be rejected by Yahoo’s mail servers”
Cloudflare Email Routing“incoming emails are rejected if they fail authentication according to the sender’s DMARC policy”

Read on October 22, 2026, in Gmail’s SMTP error reference, Microsoft’s recommended settings for Defender for Office 365, Yahoo’s sender FAQ and Cloudflare’s postmaster reference.

Microsoft is the one that documents the choice. Its anti-phishing policy lists two actions for a spoofed message from a domain at p=reject, “Quarantine the message” and “Reject the message”, and its recommended settings give the default as “Reject the message (Reject)”, with honoring the policy “Selected ($true)”. So the same p=reject can be a rejection at one Microsoft 365 organization and a quarantine at the next, and the sender cannot tell which from the record.

Once a receiver has decided to reject, rejecting during the SMTP conversation is also what the standard prefers. Section 7.2 calls it “preferable to generation of a Delivery Status Notification,” because a bounce sent later would go to the envelope address of a forged message, which may belong to someone who never sent it. The sending server learns at once, from a reply such as Gmail’s 5.7.26, that the message was refused and why, the same way the body and headers of a refusal on the web tell you which layer sent a 403 Forbidden. For a forged message, that is exactly what the domain owner wants. For a legitimate message that a forwarder broke on the way, it is the problem section 7.4 is about.

When a receiver does not apply the policy, it is supposed to say so. RFC 9989 asks receivers that deviate from it to make available “the fact of and reason for the deviation” through the aggregate report, and RFC 9990 lists the reasons a report can give: local_policy, mailing_list, trusted_forwarder, policy_test_mode and other. For the domain owner, that report is where a policy that was not applied shows up, which is one reason the rua tag matters even at p=reject.

The rollout the setup guides teach, and what RFC 9989 changed

The setup guides I read from Google and Microsoft give the same order: publish p=none, watch the reports, then raise the policy step by step. Google’s version is the most specific. Its Recommended DMARC rollout, last updated on October 20, 2026, starts with a week at p=none, because “One week is usually enough time for the daily reports to contain data representative of all your mail streams.” Then it moves to p=quarantine for a share of the failing mail: “For example, a small organization might start with 10% of their messages, but a large organization might start with 1% of their messages.” Its sample record for that step is v=DMARC1; p=quarantine; pct=5; rua=mailto:dmarc@example.com, and its setup page says “The pct tag is optional but Google recommends you include it in your DMARC record when rolling out DMARC so that you can manage the percentage of email that your DMARC policy applies to.”

Microsoft’s DMARC guide still writes pct into its record template and several of its examples, and still reports sampling in its own headers, where action=pct.reject is explained as “Sender published p=reject with pct= less than 100; this message was in the sampled percentage”. Brevo stops at the first step: “Brevo only provides the values to authenticate your domain with a DMARC p=none policy”, and the record it offers is v=DMARC1; p=none; rua=mailto:rua@dmarc.brevo.com. Its automatic setup also asks to replace what is already there: “If your domain already has a DMARC record, you’ll be asked if you want to replace it with Brevo’s DMARC record.” On a domain already at p=reject, accepting that prompt would lower it to p=none, which is worth knowing before you click through it.

RFC 9989 took pct out of the record. Its appendix on the change explains why: “Operational experience showed that the ‘pct’ tag was usually not accurately applied, unless the value specified was either 0 or 100 (the default), and the inaccuracies with other values varied widely from one implementation to another.” The IANA registry of DMARC tags now lists pct as historic, a status RFC 9989 defines as “considered deprecated and is not expected to be in use in any current implementation”. What survives is the job that pct=0 had taken on, since intermediaries had come to read it as a signal to rewrite the From line. The new t tag keeps that job: with t=y, “if the policy is ‘reject’ and the value of the ‘t’ tag is ‘y’, a policy of ‘quarantine’ will be applied to failing messages”.

So the gradual ramp in those guides rests on a tag that the current standard has retired and that, by the standard’s own account, was applied inconsistently even when it was current. A record with pct=5 is not broken, and a receiver that still samples, as Microsoft 365 documents, may honor it. But it does not mean the same thing at every receiver, and nothing in RFC 9989 defines what a receiver should do with it. None of the Google, Microsoft, Yahoo or Brevo pages I read on October 22 mentions the t tag.

The length of the first step differs too. Google says a week at p=none is usually enough. RFC 9989 says “it may take many months of consuming DMARC aggregate reports before a Domain Owner reaches the point where it is sure that it is properly authenticating all of its mail,” and for domains whose users post to mailing lists it asks for “publishing ‘p=none’ for at least a month, followed by publishing ‘p=quarantine’ for an equally long period of time” before any p=reject. For those domains it goes further and says they “SHOULD NOT publish Domain Owner Assessment Policies of ‘p=reject’” at all.

Google’s staged rollout with pct, the RFC 9989 changes, and this site’s direct path to p=reject, in three rows
Google’s guide ramps up with pct, which RFC 9989 retired in May 2026. This site started at reject, and its one failure on record cost five of its own server’s messages.

What this site did instead, and what it cost

This site skipped the ramp. Its record has said p=reject since the site’s first days in late August 2026, when the domain sent no mail at all and its SPF record was v=spf1 -all. For a domain that sends nothing, that pair is the clearest statement there is. When Brevo was being set up, on August 30 and 31, the plan was to follow the guides and drop to p=none while testing, so that a mistake would show up in the reports instead of being lost. It never happened. The first two messages through Brevo were delivered on August 31 with the policy still at p=reject, and the headers read on October 4 show what carries mail on that path: a DKIM signature with this domain in it. The policy has not changed since.

The mistake came anyway, from the one sender nobody had planned for. The server itself sends mail, such as cron output and system notices, and in early September five of those messages were rejected, as described above. That is the one loss on record while the policy has been at p=reject: five messages, from a sender the inventory left out, found through an aggregate report.

Would p=none have saved them? I cannot say it would have. Those messages passed neither SPF nor DKIM, and Gmail, for one, documents a separate refusal for exactly that case in its list of SMTP errors: “This email has been blocked because the sender is unauthenticated. Gmail requires all senders to authenticate with either SPF or DKIM.” I do not know which receivers the five messages were addressed to, so I cannot tell how many a weaker policy would have let through. What p=none would not have changed is the report: the same sender would have appeared in it.

The general point is about what the ramp is for. A staged rollout protects you from senders you do not know about, which is why its first step is reading reports. On a new domain whose senders you configured yourself, the inventory is short enough to write down before the first message, and starting at p=reject is defensible, provided the inventory includes the machine itself. Mine did not. On a domain with years of mail from people and services nobody listed, the ramp is the inventory, and the standard’s estimate of many months is the one to plan around.

Reading aggregate reports, and what they cannot show

The rua tag is the half of DMARC that tells the domain owner what happened. RFC 9990 describes “daily (or more frequent) feedback reports,” sent as an XML file, normally compressed, and adds: “Typically, the reporting period will encompass a single UTC day, beginning at 0000UTC.” Each report groups messages by the address of the server that sent them, and for each group it gives a count and two separate sets of results.

The difference between those two sets is the most useful thing to know before opening one. Under policy_evaluated, the report gives the disposition the receiver applied, then for DKIM “The result of the DKIM DMARC Identifier Alignment test” and the same for SPF. Under auth_results it gives the raw results, which RFC 9990 describes as “DKIM and SPF results, uninterpreted with respect to DMARC.” For mail like this site’s Brevo mail, the two disagree by design. This is the shape of one such row. It is not a report I received: it is built from the element tables in RFC 9990, with an address reserved for documentation and a placeholder domain for the provider’s envelope:

output
<record>
  <row>
    <source_ip>192.0.2.25</source_ip>
    <count>1</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>visibleorigin.com</header_from>
    <envelope_from>bounces.esp.example</envelope_from>
  </identifiers>
  <auth_results>
    <dkim>
      <domain>visibleorigin.com</domain>
      <selector>brevo2</selector>
      <result>pass</result>
    </dkim>
    <spf>
      <domain>bounces.esp.example</domain>
      <scope>mfrom</scope>
      <result>pass</result>
    </spf>
  </auth_results>
</record>

Read only auth_results and this row says SPF passed. Read policy_evaluated and it says SPF failed. Both are right: SPF passed for the envelope domain, and that domain does not align with the From line. A report reader that counts spf=pass from the raw results will overstate how much of a domain’s mail SPF actually protects. The disposition says none because the message passed: there was nothing to apply.

What the reports on this domain have shown so far is short. In early September, the five rejections described above. On October 1, a report from Google covered two messages sent through Brevo, both passing DKIM, none rejected. I did not reread the reports for this note: those two results come from notes taken at the time. Reports describe a domain’s mail, and reading them, like reading any mailbox, is the owner’s call. When I look at someone else’s DMARC, the record and the DNS behind it are public, while the reports and the message headers are not, and I do not read anyone’s mail without written authorization.

Two limits are worth knowing before you rely on the reports. The first is that a report only covers what reached DMARC. A message refused earlier, for instance by an SPF hard fail checked before DMARC runs, never appears; that is why the SPF record here ends in ~all, as the SPF note explains. The second is that failure reports, the per-message kind requested with ruf, are not sent by Gmail or Microsoft 365. Google’s setup page says “Gmail doesn’t support the ruf tag, which is used to send failure reports.” Microsoft’s guide says “Microsoft 365 doesn’t send DMARC Forensic reports (also known as DMARC Failure reports), even if a valid ruf=mailto: address exists in the DMARC TXT record of the source domain.” This site requests none, which suits a privacy point the standards make. Aggregate reports carry no “individual mail addresses, IP addresses of individuals, or the content of any messages”, while the addresses in ruf “receive more information that might be considered private data since it is possible for actual email content to appear in the failure reports.”

How to check a DMARC record yourself

The steps below are the ones used in this note, in the order that catches the most with the least effort. Every command is read-only and none of them sends mail.

1. Fetch the record from a public resolver, and count it

shell
$ dig +short TXT _dmarc.example.com @1.1.1.1

Exactly one answer should begin with v=DMARC1. Two cancel each other. If there is none at a subdomain you send from, receivers walk up to the organizational domain and use its record, with sp or np if it has them.

2. Write out the record a receiver will actually apply

List every tag you did not publish, with its default, as in the table above. Look there first: strict alignment copied from an example, a subdomain policy weaker than the domain’s, or a pct below 100 that you meant to raise months ago.

3. Find which identifier aligns on each path

For every service that sends mail with your domain in the From line, open a delivered message and read its Authentication-Results header, whose format is defined in RFC 8601. Compare three domains: the one after header.from on the dmarc= result, the one after header.d or header.i on the dkim= result, and the one after smtp.mailfrom on the spf= result. A path where only one of the last two matches the first is a path with one aligned identifier, like this site’s Brevo mail.

4. Read one aggregate report the right way round

Open the XML and compare policy_evaluated with auth_results for each row. The first is what DMARC decided; the second is what SPF and DKIM said before alignment was considered. A source address you do not recognize, with both aligned results failing, is a sender you forgot, a forwarder relaying your mail, or someone using your domain.

5. Check how your zone answers for a name that does not exist

shell
$ dig +dnssec A no-such-name.example.com @1.1.1.1

If the status is NOERROR with an NSEC record that lists TYPE128, your DNS provider uses compact denial, and a policy in np may never be applied. Make sp at least as strict as what you want for invented names.

6. Before you raise the policy, list every sender, including the machine

Services, platforms, the web application and the server itself, with its cron jobs and system notices. Check that every path signs with DKIM under your own domain: RFC 9989 requires valid DKIM signatures from any domain at p=reject and forbids relying on SPF alone. If you want a test step, RFC 9989’s is t=y, which asks for one level below the published policy. None of the provider pages I read mentions it, so do not assume a given receiver applies it.

DMARC record: frequently asked questions

What is a DMARC record?

It is a TXT record published at _dmarc under your domain, starting with v=DMARC1, that tells receiving servers how you want them to treat mail whose From domain is not backed by an aligned SPF or DKIM pass, and where to send reports about your domain’s mail. Its current specification is RFC 9989, published in May 2026, which replaced RFC 7489.

What does p=reject do to legitimate mail?

If a legitimate message fails DMARC, for instance because it came from a sender you forgot or a forwarder broke its signature, receivers that honor the policy refuse it. Gmail documents a 550 5.7.26 rejection, Microsoft 365 rejects by default, and Yahoo says such mail “will be rejected”. RFC 9989 asks receivers not to reject on p=reject alone, but the safe assumption is that failing mail is lost. On this site it cost five messages from the server itself.

Is the pct tag still valid in a DMARC record?

RFC 9989 removed it, and the IANA registry of DMARC tags lists it as historic. A record that contains it still parses, and some receivers still sample by it, but its meaning is no longer defined by the standard, and RFC 9989 notes that values other than 0 and 100 were usually not applied accurately. pct=100 is harmless. For a test phase, RFC 9989 uses t=y instead.

Why does DMARC fail when SPF passes?

Because DMARC needs an aligned pass. SPF checks the domain in the envelope, and when a provider puts its own domain there, SPF passes for the provider’s domain, not for the one in your From line. In an aggregate report this shows up as pass under auth_results and fail under policy_evaluated. The fix is an aligned DKIM signature, or an envelope domain that is yours.

Do I need a DMARC record if I only send a little mail?

Not to satisfy the big mailbox providers. Google and Microsoft require DMARC only from senders of more than 5,000 messages a day, Yahoo only from bulk senders, for which it says “We will not specify a volume threshold,” and in all three p=none meets the requirement. What a record does for a small domain is different: it lets you tell receivers to refuse mail that forges your From line. A domain that sends no mail at all can say so with v=DMARC1; p=reject next to v=spf1 -all.

Do I need both SPF and DKIM for DMARC to pass?

No. One aligned pass is enough, and RFC 9989 recommends using both. At p=reject, though, it requires DKIM: domains at that policy “MUST NOT rely solely on SPF to secure a DMARC pass and MUST apply valid DKIM signatures to their messages,” because forwarding usually breaks SPF and leaves DKIM intact. Two aligned identifiers are better than one, because either can fail on its own.

What a DMARC record is, and what it is not

A DMARC record is a statement about your mail, addressed to other people’s servers, and the three tags in this one say much less than the receivers do with them. Each receiver honors the policy or not under its own rules. Whether a message passes depends on how each provider signs and whose domain it puts in the envelope. The reports arrive only from receivers that send them. And the rollout advice in the largest providers’ guides still leans on a tag the standard retired in May. None of that shows when a checker says the record parses.

What does show, when the record is put next to every path its mail takes, is where it is thin. On this domain that is two things: the Brevo path, which rests on a single DKIM signature, and the server’s 1,024-bit key. Neither is failing today. Both are written down here with the commands that found them, so that they can be measured again.

Sources