<?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>Which EU countries let you check a company for free: a status table</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Fri, 07 Aug 2026 15:24:10 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/which-eu-countries-let-you-check-a-company-for-free-a-status-table-2jfe</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/which-eu-countries-let-you-check-a-company-for-free-a-status-table-2jfe</guid>
      <description>&lt;p&gt;If you are building anything that touches European business data — onboarding, invoicing, KYB, fraud checks — you will eventually ask the same question I did: which countries can I actually get company data from, for free, without an account?&lt;/p&gt;

&lt;p&gt;I could not find this written down anywhere, so I worked it out the hard way while building a supplier checker. Here it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The baseline: VIES
&lt;/h2&gt;

&lt;p&gt;The European Commission runs &lt;a href="https://ec.europa.eu/taxation_customs/vies/" rel="noopener noreferrer"&gt;VIES&lt;/a&gt;, which validates VAT numbers across all 27 member states plus Northern Ireland (&lt;code&gt;XI&lt;/code&gt;). It is free, it needs no key, and it is the obvious starting point.&lt;/p&gt;

&lt;p&gt;Two things about it are worth knowing before you build on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It answers one question: is this VAT number currently registered.&lt;/strong&gt; It does not tell you the company is solvent, trading, or that it has not been struck off. A company in liquidation keeps a cleanly resolving VAT number for months, because deregistration and insolvency are run by different authorities on different timetables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name and address are returned for 25 of the 28 jurisdictions, not all of them.&lt;/strong&gt; Germany and Spain confirm registration but publish no company name through VIES. I tested three valid numbers for each before accepting that. For those two, a yes/no is genuinely all you can honestly show.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you can go further, free
&lt;/h2&gt;

&lt;p&gt;Ten countries publish enough through a national register to add something meaningful on top of VIES:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Free register&lt;/th&gt;
&lt;th&gt;Reports company state&lt;/th&gt;
&lt;th&gt;Reports VAT-active&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Romania&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poland&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slovenia&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Estonia&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greece&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulgaria&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latvia&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Czechia&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finland&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Company state&lt;/strong&gt; means the register tells you whether a business is inactive, in liquidation, bankrupt, insolvent, terminated or struck off. This is the valuable column, and only six countries have it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VAT-active&lt;/strong&gt; matters more than it sounds. VIES cannot distinguish "this is a real company that is not VAT-registered" from "this number belongs to nobody". Three registers can.&lt;/p&gt;

&lt;p&gt;Note that Czechia and Finland are in the list but in neither of the other columns — they confirm a name and little else. Worth knowing before you design a feature around "the ten countries".&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you cannot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No free company-data source I could find at all:&lt;/strong&gt; Germany, Austria, Italy, Spain, Portugal, Lithuania, Luxembourg, Cyprus, Malta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behind an account, an API key or a payment:&lt;/strong&gt; Hungary, Ireland, Belgium, Denmark, Croatia, Netherlands, Sweden.&lt;/p&gt;

&lt;p&gt;Hungary deserves its own paragraph. It &lt;em&gt;does&lt;/em&gt; return name and address through VIES, so it looks fine at first. What is missing is company state — and I found six separate official routes to it, every single one behind a CAPTCHA. That is not a technical problem you can engineer around. It is a policy decision, and the answer is no.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UK, post-Brexit
&lt;/h2&gt;

&lt;p&gt;Great Britain left VIES on 1 January 2021. GB VAT numbers now return invalid there, and HMRC's own VAT checking API requires an approved application. If you do not hold one, what you &lt;em&gt;can&lt;/em&gt; do for free is verify the official check digit locally — which proves a number is impossible, never that it exists — and then link people to the free checker on GOV.UK.&lt;/p&gt;

&lt;p&gt;Northern Ireland is a separate case: &lt;code&gt;XI&lt;/code&gt; numbers go through VIES normally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Poland is the outlier worth knowing about
&lt;/h2&gt;

&lt;p&gt;Poland is the only EU member state where you can check whether a bank account belongs to a company.&lt;/p&gt;

&lt;p&gt;The Ministry of Finance publishes the accounts that VAT-registered businesses have declared, on what is known as the white list. It exists because paying into an unlisted account has tax consequences for the payer, so there is a statutory reason for the data to be public and current.&lt;/p&gt;

&lt;p&gt;Nowhere else in the EU can you do this from public data. Everywhere else, an IBAN tells you the country and the bank and absolutely nothing about who owns it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical notes for anyone building this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greece files VAT under &lt;code&gt;EL&lt;/code&gt;, not &lt;code&gt;GR&lt;/code&gt;.&lt;/strong&gt; Every ISO country code list will tell you &lt;code&gt;GR&lt;/code&gt;. VIES wants &lt;code&gt;EL&lt;/code&gt;. This will cost you an afternoon if nobody warns you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Northern Ireland is &lt;code&gt;XI&lt;/code&gt;.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement the check digits locally.&lt;/strong&gt; They are published arithmetic, they work offline, and they work when a registry is down. I did 19 countries and it is one of the highest value-per-line things in the project — it catches a transposed digit instantly with no network call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume the registers will break.&lt;/strong&gt; They change their markup without warning, and that is the real maintenance cost. It is also why I have not shipped an API: I would rather a scraper break for me than inside somebody else's production pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VIES has downtime&lt;/strong&gt;, disproportionately at weekends and quarter ends. National registers are a useful fallback for the ten countries above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the gaps matter more than the coverage
&lt;/h2&gt;

&lt;p&gt;The temptation when you build something like this is to present the ten countries as "European coverage" and let people assume the rest works too. It does not, and the honest version is more useful: sixteen of twenty-seven member states have no free route to company status at all.&lt;/p&gt;

&lt;p&gt;If you are relying on this data for anything that matters, know which column your country is in.&lt;/p&gt;

&lt;p&gt;I keep a running version of this against a free tool I maintain — &lt;a href="https://vetthisvendor.com/example?utm_source=devto" rel="noopener noreferrer"&gt;a worked example is here&lt;/a&gt; if you want to see what the combined output looks like. Happy to answer questions about any specific register in the comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>data</category>
      <category>software</category>
    </item>
    <item>
      <title>Four things get called "someone is spoofing my domain". DMARC fixes one.</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:52:25 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/four-things-get-called-someone-is-spoofing-my-domain-dmarc-fixes-one-2ajb</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/four-things-get-called-someone-is-spoofing-my-domain-dmarc-fixes-one-2ajb</guid>
      <description>&lt;p&gt;Someone forwards you a message. It has your company's address on it and you did not send it. The obvious next move is to go and publish &lt;code&gt;p=reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wait. Four quite different attacks get reported in exactly those words, and &lt;strong&gt;DMARC only fixes one of them.&lt;/strong&gt; Enforcing against the wrong one costs weeks and changes nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triage first: look at the actual From address
&lt;/h2&gt;

&lt;p&gt;Not the display name your mail client shows you. The address.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;What fixes it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;billing@example.com&lt;/code&gt; — your exact domain&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Domain spoofing.&lt;/strong&gt; Anyone can write any address into a &lt;code&gt;From:&lt;/code&gt; header.&lt;/td&gt;
&lt;td&gt;SPF + DKIM + DMARC at &lt;code&gt;p=reject&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Your Company &amp;lt;randomuser@gmail.com&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Display-name impersonation.&lt;/strong&gt; The address is theirs. Only the label is yours.&lt;/td&gt;
&lt;td&gt;Nothing in your DNS. Receiver-side rules and staff training.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;billing@examp1e.com&lt;/code&gt;, &lt;code&gt;example-inc.com&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Lookalike domain.&lt;/strong&gt; A different domain that reads like yours.&lt;/td&gt;
&lt;td&gt;Monitoring and takedowns. Your DMARC record has no authority over it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Genuinely from your account, in your Sent folder&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Compromised account.&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Password, revoke sessions, and check for forwarding rules the attacker added.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Only the first row is domain spoofing.&lt;/strong&gt; It's also the only one where a stranger can put &lt;em&gt;your&lt;/em&gt; domain in front of your customers, which is why it's worth fixing first and why DMARC exists.&lt;/p&gt;

&lt;p&gt;If you can't tell which you're looking at, the raw headers will settle it — the &lt;code&gt;Authentication-Results&lt;/code&gt; header names the domain that actually authenticated.&lt;/p&gt;

&lt;h2&gt;
  
  
  "I'm getting bounces for mail I never sent"
&lt;/h2&gt;

&lt;p&gt;This is how most people discover the problem, and it's alarming in a misleading way.&lt;/p&gt;

&lt;p&gt;A spammer sent to a few thousand addresses with your address forged as the sender. Some of those didn't exist. Those servers bounced the messages back to the forged sender — you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does not mean anything of yours was accessed.&lt;/strong&gt; No password is required to type your address into a header. It's evidence your domain is being forged, and a good reason to get to enforcement, but it is not a breach.&lt;/p&gt;

&lt;p&gt;One exception worth ruling out immediately: if the bounces correspond to messages that appear in your &lt;strong&gt;own Sent folder&lt;/strong&gt;, that's a compromised account, not spoofing. Different problem, much more urgent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fixing the real thing takes weeks
&lt;/h2&gt;

&lt;p&gt;Worth understanding the mechanism, because it explains the timeline.&lt;/p&gt;

&lt;p&gt;Nothing in the original design of email verifies the &lt;code&gt;From:&lt;/code&gt; header. SMTP accepts whatever a sender writes. SPF, DKIM and DMARC are a layer bolted on afterwards that lets you publish a claim about who may send as you, and lets receivers act on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Get SPF or DKIM passing for every legitimate sender.&lt;/strong&gt; Not just your mailbox provider — the CRM, the invoicing system, the ticketing tool, the booking form. And each must pass &lt;em&gt;aligned&lt;/em&gt; with your domain. A vendor sending from its own envelope domain can pass SPF perfectly and still fail DMARC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Publish DMARC at &lt;code&gt;p=none&lt;/code&gt; with a reporting address.&lt;/strong&gt;&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=none; rua=mailto:dmarc@example.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This blocks nothing. It tells you who is sending as you — including the forger, and including the three internal systems you'd forgotten about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Read the reports for four to six weeks, then enforce.&lt;/strong&gt; Long enough that monthly and quarterly senders show up. Then &lt;code&gt;p=quarantine&lt;/code&gt;, then &lt;code&gt;p=reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's no shortcut here that doesn't risk blocking your own mail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;sp=&lt;/code&gt; footgun
&lt;/h2&gt;

&lt;p&gt;Attackers move to subdomains once the parent is unusable, because &lt;code&gt;billing.example.com&lt;/code&gt; is just as convincing to a recipient and is usually left unprotected.&lt;/p&gt;

&lt;p&gt;Subdomains &lt;strong&gt;inherit &lt;code&gt;p=&lt;/code&gt;&lt;/strong&gt; unless you override it, so a bare &lt;code&gt;p=reject&lt;/code&gt; already covers them.&lt;/p&gt;

&lt;p&gt;The mistake is publishing &lt;code&gt;sp=none&lt;/code&gt; alongside it — almost always copied from an example record found somewhere — which reopens precisely the door you just shut. If you don't have a specific reason for an &lt;code&gt;sp=&lt;/code&gt; tag, don't publish one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DMARC will not do, whatever the vendor said
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not stop lookalike domains.&lt;/strong&gt; &lt;code&gt;examp1e.com&lt;/code&gt; belongs to someone else. Your DNS says nothing about it, and &lt;code&gt;p=reject&lt;/code&gt; on your domain has no effect on theirs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not stop display-name impersonation.&lt;/strong&gt; That message is properly authenticated &lt;em&gt;for the sender's own domain&lt;/em&gt;. There is no authentication failure for DMARC to act on. This is now the more common attack against small organisations — precisely because DMARC adoption made the easy version harder.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It does not stop your name being used in the message body.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not apply retroactively.&lt;/strong&gt; Mail already delivered stays delivered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does is remove your domain from the attacker's toolkit. That's worth the few weeks. The rest is a filtering and training problem, not a DNS one.&lt;/p&gt;




&lt;p&gt;I maintain &lt;a href="https://notspoofed.com/?utm_source=devto" rel="noopener noreferrer"&gt;notspoofed&lt;/a&gt; — a free, no-signup SPF/DKIM/DMARC checker. It reports whether your policy is actually enforcing and flags an &lt;code&gt;sp=&lt;/code&gt; tag that's quietly weakening it, which is the failure above that people don't find on their own. There's also an in-browser header analyzer for the triage step; headers are parsed locally and never uploaded. Source on &lt;a href="https://github.com/josepollman-png/notspoofed" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; under MIT.&lt;/p&gt;

&lt;p&gt;If you've had the bounce-flood experience and it turned out to be something other than plain domain spoofing, I'd be interested to hear which of the four it was.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What I learned reading ten EU company registers</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Wed, 05 Aug 2026 00:34:46 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/what-i-learned-reading-ten-eu-company-registers-8i2</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/what-i-learned-reading-ten-eu-company-registers-8i2</guid>
      <description>&lt;p&gt;I built a free tool that checks a supplier before you pay them. The part that took most of the work, and taught me most, was reading ten national company registers instead of relying on the EU's own VIES service.&lt;/p&gt;

&lt;p&gt;This is what I found out, mostly so the next person doesn't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "the VAT number is valid"
&lt;/h2&gt;

&lt;p&gt;VIES — the European Commission's VAT Information Exchange System — answers one question: is this VAT number currently registered. That sounds like the question you want answered. It isn't.&lt;/p&gt;

&lt;p&gt;A company that has gone into liquidation keeps a cleanly resolving VAT number in VIES. So does one that has been struck off the register. Deregistration and insolvency are run by different authorities on different timetables, and the gap between "this company has stopped being a going concern" and "the VAT number stops validating" can be months.&lt;/p&gt;

&lt;p&gt;So you can check a supplier, get a green tick, and be looking at an insolvency estate.&lt;/p&gt;

&lt;p&gt;The national registers know. VIES doesn't ask them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten registers, and what each actually gives you
&lt;/h2&gt;

&lt;p&gt;I found free, public, machine-readable-enough sources for ten countries: &lt;strong&gt;Bulgaria, Czechia, Estonia, Finland, France, Greece, Latvia, Poland, Romania and Slovenia.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They are not equivalent, and this is the thing I'd have liked written down somewhere before I started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Six of them report company *state&lt;/strong&gt;* — inactive, in liquidation, bankrupt, insolvent, terminated, ceased, struck off: Romania, Estonia, France, Greece, Bulgaria, Latvia. This is the valuable one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three report whether the company is actually VAT-active&lt;/strong&gt; — Poland, Romania, Slovenia. That matters more than it sounds, because VIES does not distinguish "this is a real company that isn't VAT-registered" from "this number belongs to nobody".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rest give you a name&lt;/strong&gt; and not much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Czechia, for instance, is in the ten but in neither of the other two groups. It confirms a name. That's it. Worth knowing before you build a feature around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Poland is the interesting one
&lt;/h2&gt;

&lt;p&gt;Poland is the only EU member state where you can check whether a bank account belongs to the supplier.&lt;/p&gt;

&lt;p&gt;Not because of anything clever — because the Ministry of Finance publishes the accounts that VAT-registered businesses have declared, on what's known as the white list. It exists because paying an unlisted account has tax consequences for the &lt;em&gt;payer&lt;/em&gt;, so there's a statutory reason for the data to be public and current.&lt;/p&gt;

&lt;p&gt;Nowhere else in the EU can you do this from public data. In every other country, an IBAN tells you the country and the bank and nothing whatsoever about who owns it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it gets ugly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nine countries have no free company-data source I could find at all:&lt;/strong&gt; Germany, Austria, Italy, Spain, Portugal, Lithuania, Luxembourg, Cyprus, Malta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seven more are gated&lt;/strong&gt; behind an account, an API key or a payment: Hungary, Ireland, Belgium, Denmark, Croatia, Netherlands, Sweden.&lt;/p&gt;

&lt;p&gt;Hungary deserves a specific mention. It &lt;em&gt;does&lt;/em&gt; return a name and address through VIES. What's missing is company state — and I found six separate official routes to it, every one of them behind a CAPTCHA. That's not a technical problem, it's a policy one, and no amount of engineering fixes it.&lt;/p&gt;

&lt;p&gt;Germany and Spain are a different flavour of annoying: VIES confirms registration for both, but neither publishes a company name through it. I tested three valid numbers for each to be sure I wasn't holding it wrong. For those two, all you can honestly show is a yes/no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small things that cost me time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greece files VAT under &lt;code&gt;EL&lt;/code&gt;, not &lt;code&gt;GR&lt;/code&gt;.&lt;/strong&gt; Every list of ISO country codes will tell you &lt;code&gt;GR&lt;/code&gt;. VIES wants &lt;code&gt;EL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Northern Ireland is &lt;code&gt;XI&lt;/code&gt;.&lt;/strong&gt; Post-Brexit, NI numbers go through VIES; Great Britain numbers don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GB is not available&lt;/strong&gt; unless you hold an approved application for HMRC's VAT API. I don't. What you can do for free is verify the official check digit locally — which proves a number is impossible, never that it exists — and then link to the free checker on GOV.UK, which answers the user's actual question in ten seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check digits are arithmetic&lt;/strong&gt;, so they work when the registry is down. I implemented them for 19 countries. This turned out to be one of the highest-value-per-line things in the project: it catches a transposed digit immediately, offline, with no network call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registers change their markup without warning.&lt;/strong&gt; This is the real maintenance cost, and the main reason I haven't shipped an API — I'd rather a scraper break for me than in someone else's production pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design decision I'd defend hardest
&lt;/h2&gt;

&lt;p&gt;The tool shows five separate results and deliberately refuses to combine them into a score or a "safe to pay" verdict.&lt;/p&gt;

&lt;p&gt;That's not modesty, it's the whole point. The most common invoice fraud is bank-detail redirection: a real supplier's mailbox is compromised and only the account number on the invoice changes. The company is real. The VAT number is valid. The goods were delivered. The sanctions check is clean. Every individual check passes, because everything &lt;em&gt;is&lt;/em&gt; genuine except the destination of the money.&lt;/p&gt;

&lt;p&gt;A single green tick would be a confident lie about the one attack that actually happens. So there are five answers, and a standing instruction to confirm bank details by phone on a number you already had.&lt;/p&gt;

&lt;p&gt;If you're building anything in this space, that's the trap: the aggregate score is the most saleable feature and the most dishonest one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring stack, on purpose
&lt;/h2&gt;

&lt;p&gt;The whole thing runs in about 300 MB on a single VPS in Helsinki. No account, no cookies, no ads, no third-party analytics. The full IBAN is never stored or displayed — only country code, check digits and last four — and saved results are deleted after 90 days.&lt;/p&gt;

&lt;p&gt;There's no API and no bulk checking. For a developer audience that's probably the whole review, and it's a fair criticism. It's one person maintaining sixteen fragile integrations, and an API is the fastest way to turn that into an operations job I can't staff.&lt;/p&gt;

&lt;p&gt;A worked example, if you'd rather see the output than type a VAT number: &lt;a href="https://vetthisvendor.com/example?utm_source=devto" rel="noopener noreferrer"&gt;vetthisvendor.com/example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to go into any of the registry scraping in the comments — that was most of the work and most of the pain.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>softwaredevelopment</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Every DKIM checker is guessing — including mine</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:53:34 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/every-dkim-checker-is-guessing-including-mine-1klb</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/every-dkim-checker-is-guessing-including-mine-1klb</guid>
      <description>&lt;p&gt;Almost every DNS record you care about can be looked up. Ask for the SPF record, you get it. Ask for &lt;code&gt;_dmarc&lt;/code&gt;, it's there.&lt;/p&gt;

&lt;p&gt;DKIM doesn't work that way, and almost nobody building tooling around it says so out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  You cannot list selectors. At all.
&lt;/h2&gt;

&lt;p&gt;A DKIM public key lives at:&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;selector&amp;gt;._domainkey.&amp;lt;domain&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The selector is an arbitrary label chosen by whoever set up signing. Google Workspace uses &lt;code&gt;google&lt;/code&gt;. Microsoft 365 uses &lt;code&gt;selector1&lt;/code&gt; and &lt;code&gt;selector2&lt;/code&gt;. Mailchimp uses &lt;code&gt;k1&lt;/code&gt;. Amazon SES generates three random tokens per identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no DNS query that returns "every name under &lt;code&gt;_domainkey&lt;/code&gt;".&lt;/strong&gt; &lt;code&gt;ANY&lt;/code&gt; doesn't do it. Zone transfers are refused by every sane nameserver. To read a DKIM key, you must already know its name.&lt;/p&gt;

&lt;p&gt;Which means every DKIM checker in existence — mine included — is guessing from a list.&lt;/p&gt;

&lt;p&gt;So when a tool tells you &lt;strong&gt;"no DKIM found"&lt;/strong&gt;, the honest translation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We tried our list of forty or fifty common selectors and none of them matched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a genuinely different statement from "you have no DKIM", and the gap between the two is where people waste afternoons.&lt;/p&gt;

&lt;p&gt;Amazon SES makes it concrete. Its selectors are random tokens. &lt;strong&gt;No guess list will ever find them.&lt;/strong&gt; A domain signing perfectly through SES will read as "no DKIM" on every guessing tool, forever. If your checker doesn't tell you that, it's misleading you by omission.&lt;/p&gt;

&lt;h2&gt;
  
  
  The opposite failure: selectors that were never there
&lt;/h2&gt;

&lt;p&gt;Now the direction that's actually dangerous, because it produces confident output instead of a shrug.&lt;/p&gt;

&lt;p&gt;Some domains publish a &lt;strong&gt;wildcard DNS record&lt;/strong&gt;, which answers every name under the domain. On such a domain:&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 totally-made-up-selector._domainkey.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...returns something. Not because that selector exists. Because the wildcard matches everything.&lt;/p&gt;

&lt;p&gt;A checker whose test is "did I get an answer?" will report a dozen imaginary selectors on these domains, and it will look authoritative doing it.&lt;/p&gt;

&lt;p&gt;The obvious defence is to require the answer to actually parse as DKIM — specifically to carry a &lt;code&gt;p=&lt;/code&gt; tag, which &lt;a href="https://www.rfc-editor.org/rfc/rfc6376" rel="noopener noreferrer"&gt;RFC 6376&lt;/a&gt; makes mandatory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's necessary, and it isn't sufficient.&lt;/strong&gt; Here's the one that got me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*._domainkey.example.com.  TXT  "v=DKIM1; p="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a structurally valid DKIM record with an empty key. It passes the &lt;code&gt;p=&lt;/code&gt; test. So every guessed selector comes back looking like a &lt;strong&gt;revoked key&lt;/strong&gt; — and now your tool is telling someone they have thirty revoked DKIM selectors, which is both alarming and entirely fictional.&lt;/p&gt;

&lt;p&gt;The only defence I've found that holds: &lt;strong&gt;query a deliberately nonsensical selector first, then discard any result identical to what the wildcard returned.&lt;/strong&gt; Establish the baseline lie, then subtract it.&lt;/p&gt;

&lt;p&gt;If a checker has ever told you that you have dozens of revoked selectors, this is what happened. Ignore it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding yours for certain
&lt;/h2&gt;

&lt;p&gt;Two methods, neither of which involves guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read a message you sent.&lt;/strong&gt; Most reliable, and needs access to nothing. Send yourself a message, view the raw source (&lt;code&gt;⋮ → Show original&lt;/code&gt; in Gmail), and find:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;DKIM-Signature&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=example.com; s=selector1; t=1785453729;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;s=&lt;/code&gt; is your selector. &lt;code&gt;d=&lt;/code&gt; is the signing domain — &lt;strong&gt;check that &lt;code&gt;d=&lt;/code&gt; matches your visible &lt;code&gt;From:&lt;/code&gt; domain&lt;/strong&gt;, because if it doesn't, DKIM is passing and not aligning, and DMARC fails anyway.&lt;/p&gt;

&lt;p&gt;Do this once per sending system. Each has its own selector and they all have to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or check the provider's console.&lt;/strong&gt; Every platform exposes this under "authentication", "verified domains" or "DKIM" — which is also where you switch signing on if it turns out it was never enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the record once you have the name
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short TXT selector1._domainkey.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;p=&lt;/code&gt; with nothing after it means revoked.&lt;/strong&gt; If anything is still signing with that selector, that mail is failing DKIM right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A missing &lt;code&gt;v=&lt;/code&gt; tag is fine.&lt;/strong&gt; RFC 6376 only recommends it. Plenty of production records start straight at &lt;code&gt;k=rsa&lt;/code&gt; and validate perfectly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key length.&lt;/strong&gt; 1024-bit RSA is still accepted everywhere but is no longer considered strong. 2048 is the norm. Rotate through your provider, not by hand-editing DNS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CNAME instead of a TXT is normal.&lt;/strong&gt; Most platforms have you delegate the selector to them so they can rotate keys without touching your zone. Follow it and you should land on a valid key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Worth stealing: the revoke-everything wildcard
&lt;/h2&gt;

&lt;p&gt;The wildcard pattern that breaks checkers is genuinely useful if you own a domain that sends no mail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*._domainkey.example.com.  TXT  "v=DKIM1; p="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That declares every selector on the domain revoked. Combined with &lt;code&gt;v=spf1 -all&lt;/code&gt; and &lt;code&gt;p=reject&lt;/code&gt;, it's an unambiguous statement that this domain sends nothing and any signature claiming otherwise is forged.&lt;/p&gt;

&lt;p&gt;And it doesn't paint you into a corner: &lt;strong&gt;DNS wildcards only apply where no exact match exists&lt;/strong&gt;, so publishing a real selector later takes precedence automatically. One working key coexists happily with a revoke-everything wildcard.&lt;/p&gt;

&lt;p&gt;Most parked and brand-protection domains have SPF and DMARC locked down and leave DKIM wide open. This closes it.&lt;/p&gt;




&lt;p&gt;I maintain &lt;a href="https://notspoofed.com/?utm_source=devto" rel="noopener noreferrer"&gt;notspoofed&lt;/a&gt; — a free, no-signup SPF/DKIM/DMARC checker. It guesses fifty selectors like everything else, but it says so, it verifies each hit is a real parseable signing key rather than a wildcard echo, and it runs the nonsense-selector baseline described above so it won't invent thirty revoked keys for you. I built that last part because I got it wrong first. Source is on &lt;a href="https://github.com/josepollman-png/notspoofed" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; under MIT.&lt;/p&gt;

&lt;p&gt;If you've seen a checker confidently report selectors that don't exist, I'd like to know which — that failure mode is more common than it should be.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SPF passed. DKIM passed. DMARC failed. Nothing is broken.</title>
      <dc:creator>Jose Pollman</dc:creator>
      <pubDate>Mon, 03 Aug 2026 19:50:40 +0000</pubDate>
      <link>https://dev.to/jose_pollman_fa7c6ec43cdd/spf-passed-dkim-passed-dmarc-failed-nothing-is-broken-22kk</link>
      <guid>https://dev.to/jose_pollman_fa7c6ec43cdd/spf-passed-dkim-passed-dmarc-failed-nothing-is-broken-22kk</guid>
      <description>&lt;p&gt;This header block is where most people's understanding of email authentication falls apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;Authentication-Results&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; mx.google.com;
  spf=pass smtp.mailfrom=bounce@esp-vendor.net;
  dkim=pass header.d=esp-vendor.net;
  dmarc=fail (p=REJECT) header.from=yourcompany.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two passes and a fail. No malformed record anywhere. Nothing to fix in DNS. And the mail is being rejected.&lt;/p&gt;

&lt;p&gt;Every part of that is correct behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question DMARC actually asks
&lt;/h2&gt;

&lt;p&gt;DMARC does not ask "did SPF pass?"&lt;/p&gt;

&lt;p&gt;It asks: &lt;strong&gt;did SPF or DKIM pass for the same domain that appears in the &lt;code&gt;From:&lt;/code&gt; header?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three domains in play in any message, and only one of them is visible to a human:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Identifier&lt;/th&gt;
&lt;th&gt;Lives in&lt;/th&gt;
&lt;th&gt;Who sees it&lt;/th&gt;
&lt;th&gt;Checked by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;From:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The message headers&lt;/td&gt;
&lt;td&gt;Your recipient&lt;/td&gt;
&lt;td&gt;Nothing, on its own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Envelope sender (&lt;code&gt;MAIL FROM&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The SMTP conversation&lt;/td&gt;
&lt;td&gt;Nobody&lt;/td&gt;
&lt;td&gt;SPF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DKIM &lt;code&gt;d=&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The signature header&lt;/td&gt;
&lt;td&gt;Nobody&lt;/td&gt;
&lt;td&gt;DKIM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SPF authenticates the envelope sender. DKIM authenticates the signing domain. &lt;strong&gt;Neither of them is the &lt;code&gt;From:&lt;/code&gt; header.&lt;/strong&gt; DMARC exists entirely to insist that at least one of them matches it.&lt;/p&gt;

&lt;p&gt;In the header above, SPF authenticated &lt;code&gt;esp-vendor.net&lt;/code&gt; and DKIM signed as &lt;code&gt;esp-vendor.net&lt;/code&gt;. The recipient sees &lt;code&gt;yourcompany.com&lt;/code&gt;. Nothing authenticated &lt;code&gt;yourcompany.com&lt;/code&gt;, so DMARC fails.&lt;/p&gt;

&lt;p&gt;It has to work this way. If DMARC accepted "SPF passed" without the matching step, anyone with a mailbox at a provider with a valid SPF record could send as you and pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you get here without doing anything wrong
&lt;/h2&gt;

&lt;p&gt;You sign up for a marketing platform, an invoicing tool, or a helpdesk. You set the From address to &lt;code&gt;hello@yourcompany.com&lt;/code&gt; because that is what your customers should see.&lt;/p&gt;

&lt;p&gt;The platform sends from its own infrastructure with its own envelope domain — it has to, because that is where bounces go. SPF passes, for them. Your &lt;code&gt;From:&lt;/code&gt; says you.&lt;/p&gt;

&lt;p&gt;Those do not align. If nothing is DKIM-signed with your domain, every message from that platform fails DMARC.&lt;/p&gt;

&lt;p&gt;This is the single most common cause of "we set up SPF and it still doesn't work".&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that feels right and does nothing
&lt;/h2&gt;

&lt;p&gt;Adding &lt;code&gt;include:esp-vendor.net&lt;/code&gt; to your SPF record.&lt;/p&gt;

&lt;p&gt;It is the intuitive move. It changes nothing.&lt;/p&gt;

&lt;p&gt;That include authorises their servers to send &lt;strong&gt;for your envelope domain&lt;/strong&gt;. Their mail still uses &lt;strong&gt;their&lt;/strong&gt; envelope domain, so the alignment comparison is untouched. You have spent one of your &lt;a href="https://dev.to/jose_pollman_fa7c6ec43cdd/your-spf-record-can-be-valid-published-and-completely-ignored-bi9"&gt;ten SPF DNS lookups&lt;/a&gt; for no benefit at all.&lt;/p&gt;

&lt;p&gt;If your reaction to a &lt;code&gt;dmarc=fail&lt;/code&gt; is to edit your SPF record, stop and read the &lt;code&gt;Authentication-Results&lt;/code&gt; header first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relaxed vs strict, and the trap in the middle
&lt;/h2&gt;

&lt;p&gt;Alignment has two modes, set per-mechanism with &lt;code&gt;aspf=&lt;/code&gt; and &lt;code&gt;adkim=&lt;/code&gt; in your DMARC record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relaxed&lt;/strong&gt; (&lt;code&gt;r&lt;/code&gt;, the default) — organisational domains must match. &lt;code&gt;mail.example.com&lt;/code&gt; aligns with &lt;code&gt;example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict&lt;/strong&gt; (&lt;code&gt;s&lt;/code&gt;) — exact match only. &lt;code&gt;mail.example.com&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; align with &lt;code&gt;example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relaxed is right for almost everyone. Strict is worth it only if you are confident every sender uses the exact apex domain, and it breaks the day someone starts sending from a subdomain.&lt;/p&gt;

&lt;p&gt;Here is the part that matters if you are implementing this yourself: &lt;strong&gt;"organisational domain" is not "the last two labels".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;attacker.co.uk&lt;/code&gt; and &lt;code&gt;victim.co.uk&lt;/code&gt; share their last two labels and have no relationship whatsoever. Getting this right means consulting the &lt;a href="https://publicsuffix.org/" rel="noopener noreferrer"&gt;Public Suffix List&lt;/a&gt;, not counting dots. A checker that counts dots will tell you two unrelated domains are aligned, which is the worst possible direction to be wrong in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading it from your own mail
&lt;/h2&gt;

&lt;p&gt;Send a message to a mailbox at a &lt;em&gt;different&lt;/em&gt; provider. Open the delivered copy and view the source — &lt;code&gt;⋮ → Show original&lt;/code&gt; in Gmail, &lt;code&gt;View message details&lt;/code&gt; in Outlook.&lt;/p&gt;

&lt;p&gt;Then compare exactly three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;From: Your Company &amp;lt;hello@yourcompany.com&amp;gt;     &amp;lt;- the domain that must be matched
spf=pass smtp.mailfrom=bounce@esp.net          &amp;lt;- authenticated esp.net
dkim=pass header.d=esp.net                     &amp;lt;- signed by esp.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If neither the &lt;code&gt;smtp.mailfrom&lt;/code&gt; domain nor the DKIM &lt;code&gt;d=&lt;/code&gt; matches your From domain, DMARC fails regardless of how correct your SPF record is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use a message you received, not one from your Sent folder.&lt;/strong&gt; Sent copies have no &lt;code&gt;Authentication-Results&lt;/code&gt; header at all — that verdict is written by the &lt;em&gt;receiving&lt;/em&gt; server. Nearly everyone trips on this the first time and concludes their headers are broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it properly
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Get DKIM signing with your own domain.&lt;/strong&gt; Every serious sending platform supports it. It is usually called "authenticate your domain", "domain authentication" or "custom DKIM", and it hands you a selector to publish in DNS. Once the platform signs with &lt;code&gt;d=yourcompany.com&lt;/code&gt;, DKIM aligns and DMARC passes no matter what the envelope sender says.&lt;/p&gt;

&lt;p&gt;There is a second reason to prefer DKIM, and it is the one that bites later: &lt;strong&gt;DKIM survives forwarding and SPF does not.&lt;/strong&gt; The moment a message is forwarded, the forwarding server is not in your SPF record. Every mailing list and every user-configured forward becomes a DMARC failure if SPF alignment is all you have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optionally, also set a custom return-path.&lt;/strong&gt; Some platforms let you point the envelope sender at a subdomain of yours — "custom return-path" or "custom bounce domain". You publish a CNAME, the envelope domain becomes &lt;code&gt;bounce.yourcompany.com&lt;/code&gt;, and that aligns under relaxed. This fixes only the SPF half and still dies on forwarding, so do it &lt;em&gt;as well as&lt;/em&gt; DKIM, not instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you enforce
&lt;/h2&gt;

&lt;p&gt;This is the actual reason the &lt;code&gt;p=none&lt;/code&gt; → &lt;code&gt;quarantine&lt;/code&gt; → &lt;code&gt;reject&lt;/code&gt; order exists.&lt;/p&gt;

&lt;p&gt;Run at &lt;code&gt;p=none&lt;/code&gt; with a reporting address and read the aggregate reports. They tell you which sources pass SPF and DKIM &lt;strong&gt;with alignment&lt;/strong&gt; — a different and much smaller set than the sources that merely pass SPF. Move to &lt;code&gt;p=quarantine&lt;/code&gt; only when that list matches the systems you expect to be there.&lt;/p&gt;

&lt;p&gt;Skip it and you will find your misaligned senders the hard way: by having their mail rejected.&lt;/p&gt;




&lt;p&gt;I maintain &lt;a href="https://notspoofed.com/?utm_source=devto" rel="noopener noreferrer"&gt;notspoofed&lt;/a&gt; — a free, no-signup checker for SPF, DKIM and DMARC. It has an in-browser header analyzer that does the three-way alignment comparison above and shows relaxed vs strict as a table; headers are parsed locally and never uploaded. It uses the Public Suffix List for organisational domains, because counting labels is wrong. Source is on &lt;a href="https://github.com/josepollman-png/notspoofed" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; under MIT.&lt;/p&gt;

&lt;p&gt;If you have hit an alignment failure with a cause that is not on this list, I would like to hear it — those are the interesting ones.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <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/?utm_source=devto" 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/?utm_source=devto" 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>
