SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Moving DMARC from p=none to p=reject

Your DMARC record has said p=none for two years. Read the reports, fix alignment for every sender, then step to quarantine and reject without losing mail.

Why DMARC p=none is not protection

A DMARC record that says p=none protects nothing. It asks receiving mail servers for reports, and it tells them to deliver failing mail exactly as they would have delivered it with no record at all. The value that stops other people sending as your domain is p=reject, and the route from one to the other is two DNS edits with a waiting period between them.

DMARC (domain-based message authentication, reporting and conformance) sits on top of two older checks. SPF (sender policy framework) lists the IP addresses allowed to send mail for a domain. DKIM (domainkeys identified mail) adds a signature to the message headers, tied to a domain in the signature's d= field. Neither check looks at the address your reader actually sees.

That is the gap DMARC fills, and the name for it is alignment. The address in the From: header is the one the mail client displays. DMARC passes if SPF passes and the envelope sender domain aligns with that From: domain, or if a valid DKIM signature carries a d= domain that aligns with it. One aligned pass is enough for the whole message.

The p= tag says what a receiver should do when neither of those happens. none means take no action and send a report. quarantine means treat the message as suspicious, which in practice means the spam folder. reject means refuse the message during the SMTP conversation, so it is never filed anywhere.

One note on versions before you edit anything. RFC 9989, published in May 2026, replaced RFC 7489 as the DMARC specification. A record you wrote in 2024 is still valid, but the tag most rollout guides tell you to use has been removed. That matters at the quarantine step below.

Why so many domains stall at p=none for two years

Most p=none records went up to satisfy somebody else's rule. Google and Yahoo have required a DMARC record from bulk senders, meaning more than 5,000 messages a day, since February 2024, and Microsoft applied the same requirement to its consumer mail domains on 5 May 2025. A policy of none clears every one of those bars. The mail flows, the deliverability ticket closes, and nothing pushes you further.

Then the reasons to stay pile up. Aggregate reports arrive as compressed XML attachments, one per receiver per day, and they are unreadable without a parser, so the folder fills up unopened. Some legitimate path fails alignment, usually a forwarder or a marketing platform, and the report cannot tell you whether that path carries mail anyone cares about. Worst of all, the cost of getting it wrong is invisible from your side: a message refused because of your policy is refused on a server you do not run, so nothing appears in your logs.

That last point deserves a direct answer rather than another year of waiting. Publishing p=reject cannot break your outbound queue. It can break mail that a person expects to receive, on somebody else's server, and you find out when they phone you. Everything below exists to remove that risk before you take it.

Read the aggregate reports before you change anything

Aggregate reports are the only feedback DMARC gives you. Each receiver sends one XML document per day per domain, listing the IP addresses that sent mail claiming your domain, how many messages each one sent, and whether SPF and DKIM aligned. RFC 9990 defines that format. You ask for the reports with the rua tag:

_dmarc.example.com.  300  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1"

Two details in that line. The TTL (time to live) is 300 seconds, which is deliberate and explained in the rollback section. fo=1 asks for a failure report whenever a check fails to produce an aligned pass, but several large receivers, Google among them, do not send failure reports at all, so the aggregate reports do the work.

If rua points at a mailbox on a different domain from the one being reported on, that other domain has to authorise it. The receiver looks up example.com._report._dmarc.reports.vendor.example and expects a TXT record containing v=DMARC1. Without that record the reports are never sent, and no error appears anywhere, which is the usual reason a new reporting dashboard stays empty for a week.

Inside a report, each record looks like this:

<record>
  <row>
    <source_ip>203.0.113.10</source_ip>
    <count>412</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>pass</dkim>
      <spf>fail</spf>
    </policy_evaluated>
  </row>
  <identifiers>
    <header_from>example.com</header_from>
  </identifiers>
</record>

Read it like this. The count is messages, not reports, so a single row can represent a whole day of newsletter traffic. The dkim and spf results inside policy_evaluated are alignment results, not raw check results, which is why a row can show spf as fail while the sending platform's own SPF check passed: it passed for a domain that is not yours. The disposition is what the receiver actually did, and under p=none it reads none on every row.

The output you want from this stage is a list of every source IP and every DKIM d= domain that legitimately sends mail as your domain. Nothing else in this guide works until that list is complete. Expect at least one name you had forgotten, an invoicing app, or an old contact form on a server nobody has touched in a year.

Fix alignment for every sender that uses your domain

Alignment fails for a small number of reasons, and each one has a fix.

The envelope sender does not match the header From. SMTP carries two sender addresses. The envelope sender, the address in MAIL FROM, is where bounces go. The header From: is what the reader sees. SPF checks the envelope. So if a relay sends with an envelope of bounces@relay.vendor.example and a header From: of hello@example.com, SPF can pass perfectly and still not align. Relaxed alignment, which is the default, accepts a subdomain of the same organisational domain, so bounces@mail.example.com does align with example.com. A different domain never aligns.

The DKIM signature carries the vendor's domain. A platform that signs with d=vendor.example hands you a valid signature that does nothing for DMARC, because it does not align with your From: header. Every serious sending platform offers branded DKIM: you publish a CNAME or a TXT selector under your own domain, and the signature then carries d=example.com. Do this for every platform on your list. It is the highest value change in the whole process, for the reason in the next section.

The SPF record exceeds ten DNS lookups. Each include:, a, mx and redirect term costs lookups, and the total limit is ten. Over the limit a receiver returns permerror, and DMARC counts anything that is not a clean pass as not a pass. Cut the unused include: terms first, because a long SPF record is usually a museum of services nobody sends through any more.

There is more than one DMARC record. Two TXT records at _dmarc cause the receiver to stop and apply no policy at all, so a second record added by a vendor's setup wizard silently switches your protection off. There must be exactly one, and it must begin with v=DMARC1.

For a self-hosted sender the fix is usually one line of signing config rather than a vendor form. On a mailcow install on a VPS the DKIM keys are generated in the admin interface and the d= domain is already yours, and Stalwart on a VPS signs from its own config in the same way. The awkward case is application mail, where a web app or a cron job hands a message to a local Postfix that relays it onward. Those messages often carry a header From: of root@mail.example.com, which aligns under the default relaxed mode, but they are still worth checking one at a time, because application mail sent through a relay is the traffic most likely to be signing with somebody else's domain.

Verify each sender by reading a message it delivered. Every large receiver writes an Authentication-Results: header, and a healthy one looks like this:

Authentication-Results: mx.google.com;
       dkim=pass header.i=@example.com header.s=mail header.b=Xk3rP1aQ;
       spf=pass (google.com: domain of bounces@mail.example.com designates 203.0.113.10 as permitted sender) smtp.mailfrom=bounces@mail.example.com;
       dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=example.com

The part that matters is header.i= or header.d= on the DKIM line, next to header.from= on the DMARC line. If those two domains do not match, that sender is not aligned, whatever the rest of the line says.

You can read your own published records from any machine with outbound DNS. These lookups read public data and change nothing on your server:

dig +short TXT _dmarc.example.com
dig +short TXT example.com
dig +short TXT mail._domainkey.example.com

The last one needs your selector name, which you copy from header.s= in the header above. If the caching behaviour of those answers is not familiar, how DNS records are published and cached covers the mechanics that the rest of this guide leans on.

Why DKIM alignment survives forwarding and SPF does not

This section decides whether p=reject is safe for your domain.

When a mailbox forwards a message onward, an alumni address, a .forward file, or a role account that copies mail to somebody's personal inbox, the forwarding server sends the message from its own IP address. Your SPF record does not list that IP, because it is not your server. The final receiver checks SPF against an address you never authorised and gets a fail. Nothing was tampered with. SPF cannot survive a hop it does not know about.

Some forwarders use SRS (sender rewriting scheme), which rewrites the envelope sender to their own domain so that SPF passes again. That repairs SPF and breaks alignment in the same move: SPF now passes for forwarder.example, which is not your domain, so DMARC still gets no aligned pass out of it.

DKIM behaves differently because the signature travels inside the message. The forwarder does not need to be authorised by anyone. As long as the body and the signed headers arrive unmodified, the signature verifies at the far end, and d=example.com still aligns with your From: header. That is the whole reason a domain with aligned DKIM on every sender can move to p=reject without losing forwarded mail, while a domain relying on SPF alone will lose it.

Mailing lists are the exception. A list that adds a subject tag or appends a footer changes signed content, so the signature fails. That is why most lists now rewrite the From: header to the list address, and why ARC (authenticated received chain) exists, so a receiver can see that an intermediary vouched for the original result. You cannot control either behaviour from your DNS. If your users post to an old list that does neither, they will see rejections, and the fix belongs on the list.

Move to quarantine, and what happened to the pct tag

Lower the TTL on the _dmarc record to 300 seconds first, and wait for the old TTL to expire before you touch the policy. This costs nothing, and it is what makes the rollback fast.

Then publish quarantine:

_dmarc.example.com.  300  IN  TXT  "v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:dmarc@example.com; fo=1"

sp= sets the policy for subdomains and defaults to whatever p= says, so it is written out here only to make the intent obvious to the next person who reads the record.

The classic advice at this point is to add pct=25 and raise it weekly. Do not build your rollout on it. RFC 9989 removed the pct tag in May 2026 and tells receivers to apply the published policy to all mail, because the tag had been implemented inconsistently for years. Its replacement is a binary flag, t=y, meaning the policy is published for testing and should not be enforced, with t=n as the default. Leaving pct=25 in a record does no damage, since receivers ignore tags they do not recognise, but treat any percentage as a hint that much of the internet will skip.

Stage by scope instead, which every receiver has always honoured. A record of p=none; sp=quarantine enforces on subdomains while the parent domain stays in reporting mode. That is useful when your application and cron mail leaves from mail.example.com and your human mail does not. Promote the parent domain once the reports for the subdomain are clean.

Quarantine has one property worth knowing before you leave it running for a month. A quarantined message lands in a spam folder and nobody is told. The sender gets no bounce, and the recipient gets no notice. Reject is louder and, in that narrow sense, kinder: the sending server receives a refusal it can show to a human. Quarantine is a checkpoint, not a safe permanent home.

Watch the reports for two full weeks. They arrive daily, so a fortnight covers the weekly billing run and the Monday morning newsletter. You are looking for one thing: a row with a real sender, a real message count, and no aligned pass.

Move to reject, and the messages senders will see

_dmarc.example.com.  300  IN  TXT  "v=DMARC1; p=reject; sp=reject; np=reject; rua=mailto:dmarc@example.com; fo=1"

np= is new in RFC 9989 and sets the policy for subdomains that do not exist in DNS. It matters because a forger will invent billing.example.com rather than reuse a name you actually publish. Receivers that implement only the older specification ignore the tag and fall back to sp=, which is the correct handling of an unknown tag, so adding it costs you nothing.

From here a failing message is refused inside the SMTP session and the sending server tells its own user. Gmail returns text like this, and the enhanced status code attached to it has changed over the years, so match on the words rather than the number:

550 5.7.26 Unauthenticated email from example.com is not accepted due to domain's DMARC policy. Please contact the administrator of example.com domain if this was a legitimate mail.

Microsoft is more explicit about which policy applied:

550 5.7.509 Access denied, sending domain [example.com] does not pass DMARC verification and has a DMARC policy of reject

Keep both strings in your notes. When a colleague forwards you a bounce, the text tells you whose policy refused the message and which domain was on the From: line, which is most of the diagnosis.

Rolling back takes one DNS edit

Change p=reject back to p=none and republish. That is the whole rollback. With a 300 second TTL, receivers that cached your record pick up the new value within five minutes, and receivers that had not cached it see it on their next lookup.

There are two honest limits on that. Some receivers cache DMARC records for longer than the TTL you publish, so a thin tail of rejections can continue after the edit. And the rollback is not retroactive. Under reject the refused mail bounced, so each sender still holds a copy and can send it again once you tell them the policy is back to none. Under quarantine the same mail is sitting in a spam folder that nobody will ever check, which is the better argument for keeping the quarantine phase short.

What breaks, and how each failure looks

Forwarded mail stops. A user's address forwards to a personal mailbox, the message arrives with SPF failing and no valid DKIM signature, and the far end refuses it. The fix is DKIM alignment on the original message. Nothing you change at the forwarder helps.

A quiet application stops mailing. Password resets and monitoring alerts often leave by a path nobody has audited for years. If those messages show up in the reports with no aligned pass, and you did not recognise the source IP when you built your list, that is the sender to fix before you promote the policy.

Newsletter mail is refused in bulk. A platform still signing with its own d= domain, or an SPF record pushed past ten lookups, fails every message at once rather than a few. Running a self-hosted Listmonk newsletter does not exempt you from this, because the alignment question is identical whether the sending software is yours or rented.

The spoofing you were worried about continues anyway. A reject policy stops mail that puts your domain in the From: header. It does nothing about mail that merely mentions your name, and nothing about signup floods aimed at an address you own, because none of that traffic claims to come from your domain.

Reject is not a one time project. Every new sending service reopens the alignment question, and each one has to be fixed before it is allowed to send rather than afterwards. That standing cost is part of what running your own mail is really worth, and it is also what keeps a forged invoice carrying your name out of a customer's inbox. An enforcing policy gives you a straight answer too when an abuse complaint reaches your provider about a message you did not send: if it carried no aligned signature, it did not come from your domain.

FAQ

Does p=none stop anyone spoofing my domain?

No. p=none is a request for reports. It tells receiving servers to handle failing mail exactly as they would with no DMARC record published, so a forged message that fails both SPF and DKIM alignment is delivered normally. What p=none does give you is a daily aggregate report from each large receiver, and that report is the input you need before moving to quarantine and then reject.

How long should I wait before moving to p=reject?

Two weeks at each step is a reasonable default, because aggregate reports arrive once a day and a fortnight covers weekly and month end sending patterns. The calendar is not the real gate. Move on when every legitimate sender on your list shows an aligned pass in the reports, and stay where you are while any of them still shows a real message count with no aligned pass.

Can I still use pct to roll out DMARC slowly?

Treat it as unreliable. RFC 9989, published in May 2026, removed the pct tag and instructs receivers to apply the published policy to all mail. Receivers ignore tags they do not recognise, so leaving pct=25 in a record breaks nothing, but you cannot assume anyone is acting on it. Stage by scope instead: publish an enforcing policy for subdomains with sp= while the parent domain stays at none, then promote the parent.

Why does forwarded mail fail DMARC when SPF and DKIM both pass at my server?

Because SPF is checked against the IP address of the server that connected, and a forwarder connects from its own address, which is not in your SPF record. If that forwarder uses SRS to rewrite the envelope sender, SPF passes for the forwarder's domain instead, and that domain does not align with your From: header, so DMARC still gets no aligned pass from SPF. A DKIM signature travels inside the message and keeps verifying as long as the body and the signed headers are untouched, which is why DKIM alignment is what makes a reject policy safe.