<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jose Pollman</title>
    <description>The latest articles on DEV Community by Jose Pollman (@jose_pollman_fa7c6ec43cdd).</description>
    <link>https://dev.to/jose_pollman_fa7c6ec43cdd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4059554%2F63cedb60-cf00-4817-8cbd-9bc1eda1c48e.png</url>
      <title>DEV Community: Jose Pollman</title>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jose_pollman_fa7c6ec43cdd"/>
    <language>en</language>
    <item>
      <title>Why your DMARC reports never arrive (and why Gmail can't be your rua address)</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Sun, 02 Aug 2026 21:02:50 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/why-your-dmarc-reports-never-arrive-and-why-gmail-cant-be-your-rua-address-4cj3</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/why-your-dmarc-reports-never-arrive-and-why-gmail-cant-be-your-rua-address-4cj3</guid>
      <description>&lt;p&gt;You published a DMARC record months ago. Every checker says it's valid. No reports have ever arrived, and at some point you stopped thinking about it and quietly concluded nobody's forging your domain.&lt;/p&gt;

&lt;p&gt;You don't know that. You just can't see.&lt;/p&gt;

&lt;p&gt;Two causes account for almost all of it, and both leave the DMARC record itself looking perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your reporting address never agreed to receive them
&lt;/h2&gt;

&lt;p&gt;This is in the spec and almost nobody knows about it.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;rua=&lt;/code&gt; address is on a &lt;strong&gt;different organisational domain&lt;/strong&gt; from the domain publishing the record, &lt;a href="https://www.rfc-editor.org/rfc/rfc7489#section-7.1" rel="noopener noreferrer"&gt;RFC 7489 section 7.1&lt;/a&gt; requires the destination to explicitly consent. The reason is obvious once you see it: otherwise anyone could point &lt;code&gt;rua=&lt;/code&gt; at your mailbox and use the world's mail infrastructure as a free DDoS.&lt;/p&gt;

&lt;p&gt;Consent is a DNS record, published by the &lt;strong&gt;receiving&lt;/strong&gt; domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;your-domain&amp;gt;._report._dmarc.&amp;lt;their-domain&amp;gt;.  TXT  "v=DMARC1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if &lt;code&gt;example.com&lt;/code&gt; sends reports to &lt;code&gt;dmarc@reports.vendor.com&lt;/code&gt;, then &lt;code&gt;vendor.com&lt;/code&gt; has to publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com._report._dmarc.reports.vendor.com.  TXT  "v=DMARC1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without it, conforming reporters send nothing at all. That includes Google, which is most of your report volume.&lt;/p&gt;

&lt;p&gt;You can watch this working in production. PayPal sends aggregate reports to a third party, and the authorisation record exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paypal.com._report._dmarc.rua.agari.com.  TXT  "v=DMARC1;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The practical consequence: not a Gmail address
&lt;/h3&gt;

&lt;p&gt;You cannot put a Gmail address in &lt;code&gt;rua=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Google is not going to publish &lt;code&gt;yourdomain.com._report._dmarc.gmail.com&lt;/code&gt; on your behalf, and you can't create records in their zone. Same for Outlook, Yahoo, or any address at a domain you don't control.&lt;/p&gt;

&lt;p&gt;This is a common first move — publish DMARC, point reports at your personal inbox, wait. Nothing arrives, and nothing is wrong with the record.&lt;/p&gt;

&lt;p&gt;If you use a commercial DMARC service they normally publish these records during onboarding, often via a wildcard. If reports never start after you sign up, check this before anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When it doesn't apply:&lt;/strong&gt; reporting to your own organisational domain needs no authorisation. &lt;code&gt;example.com&lt;/code&gt; sending to &lt;code&gt;dmarc@mail.example.com&lt;/code&gt; is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. There's no mail server behind the address
&lt;/h2&gt;

&lt;p&gt;Subtler, and easy to create by accident on a domain that only serves a website.&lt;/p&gt;

&lt;p&gt;You publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_dmarc.example.com.  TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"```

Self-referential, so no authorisation needed. Correct in every way.

But if `example.com` has **no MX record**, senders fall back to its A record — the implicit MX rule in [RFC 5321 section 5.1](https://www.rfc-editor.org/rfc/rfc5321#section-5.1). On a web-only domain that A record points at a web server, which doesn't speak SMTP. Every report bounces.

The DMARC record is flawless. You just have no mailbox.

This bites hardest on marketing sites and defensive brand registrations — exactly the domains where you published DMARC as a hardening measure and are least likely to notice the silence.

The fix doesn't require running a mail server. A forwarding service will publish MX records for you and forward `dmarc@` somewhere you actually read.

## Checking both in about thirty seconds



```bash
# 1. What does your record actually say?
dig +short TXT _dmarc.example.com

# 2. If rua points off-domain, is it authorised?
dig +short TXT example.com._report._dmarc.THEIR-DOMAIN.com
#    Expect "v=DMARC1". Anything else means reports are refused.

# 3. If rua points at your own domain, can it receive mail?
dig +short MX example.com
#    Empty means every report bounces.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A third possibility, but rule the others out first
&lt;/h2&gt;

&lt;p&gt;Some providers only send aggregate reports once they see meaningful volume from a domain. A domain sending almost nothing may legitimately get few reports, particularly from smaller receivers.&lt;/p&gt;

&lt;p&gt;That's a real explanation, but it's also the comfortable one, so don't reach for it early. The two DNS causes above are deterministic and fixable. Google will typically report on any domain with real traffic within a day or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it sounds
&lt;/h2&gt;

&lt;p&gt;The entire value of &lt;code&gt;p=none&lt;/code&gt; is the reporting. That's the whole point of the stage.&lt;/p&gt;

&lt;p&gt;If you're sitting at &lt;code&gt;p=none&lt;/code&gt; with broken reporting, you've done all the work of DMARC and got none of the benefit — no protection, because &lt;code&gt;p=none&lt;/code&gt; blocks nothing, and no visibility either. You're paying the setup cost and collecting nothing.&lt;/p&gt;

&lt;p&gt;Check it on the day you publish, not six months later when someone asks how the rollout is going.&lt;/p&gt;




&lt;p&gt;I maintain &lt;a href="https://notspoofed.com" rel="noopener noreferrer"&gt;notspoofed&lt;/a&gt;, a free checker that tests both of these — whether an external &lt;code&gt;rua=&lt;/code&gt; is actually authorised, and whether the reporting address can receive mail at all. It's the failure mode I've seen most often and the one fewest tools look for. No signup, and checked domains aren't logged.&lt;/p&gt;

&lt;p&gt;If you've had reports silently missing for months, I'd be curious which of the two it turned out to be.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your SPF record can be valid, published, and completely ignored</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Sun, 02 Aug 2026 20:55:09 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/your-spf-record-can-be-valid-published-and-completely-ignored-bi9</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/your-spf-record-can-be-valid-published-and-completely-ignored-bi9</guid>
      <description>&lt;p&gt;There's a failure mode in SPF that produces no error anywhere you'd think to look.&lt;/p&gt;

&lt;p&gt;Your record is syntactically valid. &lt;code&gt;dig&lt;/code&gt; returns it. Your DNS provider is happy. Your own test mail arrives fine. And yet some of your mail is failing authentication at the receiver, and nothing in your infrastructure will tell you.&lt;/p&gt;

&lt;p&gt;The cause is usually that you've gone past ten DNS lookups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten is a hard ceiling, not a guideline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7208#section-4.6.4" rel="noopener noreferrer"&gt;RFC 7208 section 4.6.4&lt;/a&gt; requires evaluators to cap DNS-querying terms at ten. Go past it and the result is &lt;code&gt;PermError&lt;/code&gt; — a permanent failure.&lt;/p&gt;

&lt;p&gt;The important part: receivers don't degrade gracefully. They don't evaluate the first ten and shrug at the rest. They treat the entire record as unusable. You go from "SPF configured" to "SPF absent" in one step, and the only place that's visible is in DMARC aggregate reports, which most people aren't reading yet.&lt;/p&gt;

&lt;p&gt;Six terms cost a lookup. Four are free.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Costs a lookup&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;include:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip4:&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip6:&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;exp=&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exists:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redirect=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The count isn't yours to control
&lt;/h2&gt;

&lt;p&gt;This is what catches people out. The limit applies to the whole resolution tree, not just your record.&lt;/p&gt;

&lt;p&gt;You publish four includes. One of those vendors publishes six includes of their own. You're at eleven, and your record lists four things.&lt;/p&gt;

&lt;p&gt;Nothing changed on your side. A vendor updated their record and broke yours. There is no notification for this, from anyone, ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting it by hand
&lt;/h2&gt;

&lt;p&gt;You can walk the tree with &lt;code&gt;dig&lt;/code&gt; if you want to see it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short TXT example.com | &lt;span class="nb"&gt;grep &lt;/span&gt;spf1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then for every &lt;code&gt;include:&lt;/code&gt; you find, recurse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short TXT _spf.vendor.com | &lt;span class="nb"&gt;grep &lt;/span&gt;spf1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep going until you bottom out, counting every &lt;code&gt;include:&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;mx&lt;/code&gt;, &lt;code&gt;ptr&lt;/code&gt;, &lt;code&gt;exists:&lt;/code&gt; and &lt;code&gt;redirect=&lt;/code&gt; along the way.&lt;/p&gt;

&lt;p&gt;It's tedious but instructive — you'll usually find one vendor accounting for half your budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three traps that make tools lie to you
&lt;/h2&gt;

&lt;p&gt;Doing this by hand or with a naive script, there are three places to go wrong. I've seen all three produce confidently incorrect output.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;redirect=&lt;/code&gt; uses &lt;code&gt;=&lt;/code&gt;, not &lt;code&gt;:&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It's a modifier, not a mechanism, and both humans and regexes skip it. Plenty of production domains are nothing but a redirect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hubspot.com.  TXT  "v=spf1 redirect=_hspf.hubspot.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Miss that single term and you conclude the domain uses zero lookups — when in fact every include and every cost lives behind it. If a checker tells you a domain like this uses no lookups, the checker is broken.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Void lookups have a separate budget
&lt;/h3&gt;

&lt;p&gt;A lookup that returns nothing is a &lt;em&gt;void lookup&lt;/em&gt;, and RFC 7208 caps those at &lt;strong&gt;two&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So an include pointing at a vendor you stopped paying for last year, whose record no longer exists, hurts you twice: once against the ten, and once against the void limit. Dead includes aren't free just because they resolve to nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. TXT records arrive in pieces, joined with nothing
&lt;/h3&gt;

&lt;p&gt;This is the one that bites anyone writing their own tooling.&lt;/p&gt;

&lt;p&gt;DNS transmits TXT records as one or more character-strings of at most 255 bytes. A long SPF record arrives in several chunks, and &lt;a href="https://www.rfc-editor.org/rfc/rfc7208#section-3.3" rel="noopener noreferrer"&gt;RFC 7208 section 3.3&lt;/a&gt; says they're concatenated &lt;strong&gt;with no separator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Real records split mid-token. One production record ends a chunk with &lt;code&gt;...ip4&lt;/code&gt; and starts the next with &lt;code&gt;:161.38.192.0/20&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Join them correctly and you get &lt;code&gt;ip4:161.38.192.0/20&lt;/code&gt;. Join them with a space — the obvious-looking thing to do — and you get &lt;code&gt;ip4 :161.38.192.0/20&lt;/code&gt;, which isn't valid syntax.&lt;/p&gt;

&lt;p&gt;If you're parsing SPF yourself, handle this first. If you're using someone else's tool and the output looks subtly mangled, this is usually why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it, in the order worth trying
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Remove senders you no longer use.&lt;/strong&gt; The only fix with no downside, and it's almost always available. Most domains over the limit are carrying includes for tools nobody has logged into in years, and deleting one buys back its entire subtree. Get the include list in front of whoever owns marketing and billing and ask what's still live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask the vendor for a narrower include.&lt;/strong&gt; Some publish a broad include covering all their infrastructure plus a tighter one scoped to a product or region. This is rarely documented — you have to ask support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flatten, but as little as possible.&lt;/strong&gt; Replacing &lt;code&gt;include:vendor.com&lt;/code&gt; with the IPs it resolves to turns a five-lookup subtree into zero, because &lt;code&gt;ip4:&lt;/code&gt; and &lt;code&gt;ip6:&lt;/code&gt; are free.&lt;/p&gt;

&lt;p&gt;The cost is permanent and easy to underestimate: you've taken on that vendor's maintenance burden. When they add a sending IP, their record updates and yours doesn't. Your mail from that provider starts failing SPF silently, and you find out when someone notices deliverability dropped a month ago.&lt;/p&gt;

&lt;p&gt;If you flatten:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only flatten enough to get under ten. Retiring the single most expensive include is often sufficient, and leaves everyone else free to rotate their own IPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never flatten a provider using macros.&lt;/strong&gt; A term like &lt;code&gt;exists:%{i}._spf.example.com&lt;/code&gt; expands against the connecting IP. There's no fixed set of addresses to inline, and flattening it silently drops every sender it would have matched.&lt;/li&gt;
&lt;li&gt;Re-check monthly, in a calendar, not in your head.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Worth knowing: large providers increasingly flatten their own records for this reason. &lt;code&gt;_spf.google.com&lt;/code&gt; today contains only &lt;code&gt;ip4:&lt;/code&gt; and &lt;code&gt;ip6:&lt;/code&gt; entries with no nested includes, so &lt;code&gt;include:_spf.google.com&lt;/code&gt; costs you exactly one lookup rather than four.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't aim for ten
&lt;/h2&gt;

&lt;p&gt;Ten is legal. Ten is not a target.&lt;/p&gt;

&lt;p&gt;A record sitting at exactly ten breaks the day someone in marketing signs up for one more tool, and nobody will connect those two events. Aim for seven or eight so there's headroom to add a sender without causing an outage.&lt;/p&gt;




&lt;p&gt;I maintain &lt;a href="https://notspoofed.com" rel="noopener noreferrer"&gt;notspoofed&lt;/a&gt;, a free checker that walks the whole include tree, follows &lt;code&gt;redirect=&lt;/code&gt;, counts against the real ceiling, and generates a corrected record that flattens only what it has to. It handles all three traps above, because I got them wrong first. No signup, and the domains you check aren't logged.&lt;/p&gt;

&lt;p&gt;If you've hit this in an interesting way — particularly a vendor whose record silently blew your budget — I'd like to hear about it.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
