<?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: Oleh</title>
    <description>The latest articles on DEV Community by Oleh (@oleggg1).</description>
    <link>https://dev.to/oleggg1</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%2F4086285%2F34c02ade-a401-4333-8f26-0330ad3c3f5f.jpg</url>
      <title>DEV Community: Oleh</title>
      <link>https://dev.to/oleggg1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oleggg1"/>
    <language>en</language>
    <item>
      <title>How Spamhaus blocklisted my SMTP verifier — a server that never sent an email</title>
      <dc:creator>Oleh</dc:creator>
      <pubDate>Thu, 20 Aug 2026 08:37:47 +0000</pubDate>
      <link>https://dev.to/oleggg1/how-spamhaus-blocklisted-my-smtp-verifier-a-server-that-never-sent-an-email-4335</link>
      <guid>https://dev.to/oleggg1/how-spamhaus-blocklisted-my-smtp-verifier-a-server-that-never-sent-an-email-4335</guid>
      <description>&lt;p&gt;In short: I'm building an email verification service that works via live SMTP probing. One day iCloud started rejecting my server with 550 ... rejected due to listing in Spamhaus — even though my server had never sent a single email. What follows is an investigation with a wrong hypothesis, a humbling config discovery, a bureaucratic ticket rejection, and an unexpectedly happy ending. With lessons that are worth more than the incident itself.&lt;/p&gt;

&lt;p&gt;What I'm building&lt;br&gt;
An email verification service: does this mailbox actually exist? Syntax and MX checks are the warm-up; the real question is answered by an SMTP probe: connect to the recipient's mail server, walk the dialogue up to RCPT TO:&lt;/p&gt;, and look at the reply. 250 — the mailbox exists, 550 — it doesn't. Then the probe politely hangs up. DATA is never sent, which means the service cannot send email by construction.

&lt;p&gt;Keep that in mind for the rest of the story: not a single message ever left this server.&lt;/p&gt;

&lt;p&gt;The day iCloud stopped talking to me&lt;br&gt;
At some point, probes to iCloud started failing with a verbatim:&lt;/p&gt;

&lt;p&gt;550 5.7.1 Mail from IP 207.180.207.175 was rejected due to listing in Spamhaus&lt;br&gt;
I checked zen.spamhaus.org — and yes, my IP was listed as CSS (Composite Snowshoe Spam), return code 127.0.0.3. "Snowshoe spam" is when a spammer spreads their sending thinly across many IPs so that no single one stands out. I had just been filed under that category.&lt;/p&gt;

&lt;p&gt;Here's the first trap worth knowing if you ever check yourself against blocklists: Spamhaus does not serve queries coming through public resolvers. Ask through 8.8.8.8 or 1.1.1.1 and you get 127.255.255.x — "query refused" — which is trivially easy to misread as "clean". And an empty answer means both "clean" and "the server didn't respond". Both mistakes fail in the falsely reassuring direction, so I wrapped the check in a script that distinguishes all three states instead of a one-liner.&lt;/p&gt;

&lt;p&gt;I went through eighteen blocklists: clean everywhere else, including UCEPROTECT levels 2 and 3, which list entire ranges and autonomous systems. So my hosting provider's range wasn't under a blanket ban — the problem was mine, personally.&lt;/p&gt;

&lt;p&gt;The machine itself was clean too: the local MTA listens on loopback only, the queue was empty. I wasn't an open relay.&lt;/p&gt;

&lt;p&gt;The wrong hypothesis, or how I treated a disease I didn't have&lt;br&gt;
My first model of the cause looked perfectly logical: CSS was issued for a behavioral pattern — my server knocks on dozens of unfamiliar SMTP servers per hour with short connections that drop right after RCPT. From a telemetry point of view, that genuinely resembles reconnaissance before a spam run.&lt;/p&gt;

&lt;p&gt;So I built a quota: a hard cap on how many new domains per hour the service may contact at all. Engineering-wise it's the right thing — hammering hundreds of strangers per hour is bad neighborship regardless of anything. But, as you'll see below, that was not why I got listed. A classic lesson about unverified hypotheses: I treated a disease I didn't have, exhaled with satisfaction, and waited for the listing to expire.&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;The ticket, and the reply that made me blush&lt;br&gt;
Spamhaus delisting is free, so I filed a ticket — if nothing else, to get an answer I couldn't get any other way. Two submission gotchas that may save you an hour: they won't accept requests from freemail addresses (gmail and the like), and the domain of the requester's address must match the domain in your IP's PTR record.&lt;/p&gt;

&lt;p&gt;The reply came within half an hour, and the cause was not the one I had assumed:&lt;/p&gt;

&lt;p&gt;IP:   207.180.207.175&lt;br&gt;
rDNS: mail.proofmailapi.com&lt;br&gt;
HELO: proofmail.207.180.207.175.sslip.io&lt;br&gt;
There it is. A generic-domain HELO. sslip.io is an "any IP as a domain name" service that staging setups use: handy while you don't have real DNS. My service once lived on such a staging box, and when it moved to the production domain I updated the PTR and SPF — and forgot the HELO in the config. To a receiving server, a host introducing itself via sslip.io is indistinguishable from the thousands of infected machines that do exactly the same.&lt;/p&gt;

&lt;p&gt;A remote MTA checks four things about you within the first milliseconds of the dialogue, and all four must agree:&lt;/p&gt;

&lt;p&gt;PTR — the reverse record, IP → name.&lt;br&gt;
FCrDNS — that name must resolve back to the same IP.&lt;br&gt;
HELO — the name you introduce yourself with must match the rDNS.&lt;br&gt;
MAIL FROM — the sender's domain must have an MX; some servers go further and do a sender callout — they connect to that MX and check whether the sender address is even accepted.&lt;br&gt;
Three out of four matched in my case. The third one didn't, and that was enough.&lt;/p&gt;

&lt;p&gt;Speaking of callouts — a detail that cost me a separate evening: the probe@ address the probe uses as its sender must be accepted by my own mail provider, otherwise remote servers reject the probe because of a non-existent sender. With Cloudflare Email Routing this is solved by a routing rule with the Drop action: the address is accepted (the callout sees 250), and the message is discarded after acceptance.&lt;/p&gt;

&lt;p&gt;The HELO fix took a minute: one variable in the config, sslip.io → mail.proofmailapi.com. Now PTR, the A record, and HELO are one name.&lt;/p&gt;

&lt;p&gt;The bureaucratic finale of the ticket&lt;br&gt;
I replied to the ticket: cause removed, HELO ↔ rDNS match confirmed. Forty minutes later the ticket was rejected — not on the merits, but on authorization checks. Spamhaus requires provable ownership linking the requester to the IP, and out of three conditions I passed one:&lt;/p&gt;

&lt;p&gt;condition   me&lt;br&gt;
rDNS resolves publicly and matches the mail domain  yes&lt;br&gt;
the IP's WHOIS belongs to the requester no — I rent a VPS, WHOIS shows the hosting provider&lt;br&gt;
the request originates from the IP being delisted   no — my mail physically leaves through my mail provider's infrastructure&lt;br&gt;
The last two are fundamentally unfixable for a rented VPS with forwarded mail. Looks like a dead end — but the same rejection letter contained the most valuable sentence of the entire story:&lt;/p&gt;

&lt;p&gt;Listings expire a few days after the last detection.&lt;/p&gt;

&lt;p&gt;Meaning: if the cause is truly gone, there are no new detections — and the record ages out on its own. The ticket was an accelerator, not a necessity. So the plan became: don't file a third time. Wait and measure.&lt;/p&gt;

&lt;p&gt;The finale: the listing expired by itself&lt;br&gt;
Three days after the HELO fix, the record was gone — with no action on my part, exactly as the hypothesis predicted. iCloud started talking immediately (verified with a live probe from production).&lt;/p&gt;

&lt;p&gt;And the bonus that paid for the whole investigation: once the big providers unblocked me, I could finally measure what the listing had been hiding — and discovered that Microsoft answers RCPT non-deterministically: the same non-existent address on outlook.com returns 250 or 550 depending on which backend behind the load balancer you hit, roughly 1 in 5. That's a separate story with separate consequences for anyone who trusts a single SMTP answer from Microsoft — if there's interest, I'll write it up.&lt;/p&gt;

&lt;p&gt;Lessons&lt;br&gt;
Moving from staging to production is a checklist, not a mood. PTR, SPF, HELO, MX, the callout address. One missed item out of five cost me a listing in the world's most influential blocklist.&lt;br&gt;
Don't treat a hypothesis — test it. My first model of the cause was logical, elegant, and wrong. I kept the quota, but on its own merits, not as "the Spamhaus cure".&lt;br&gt;
A blocklist self-check must distinguish three states: "clean", "listed", "refused to answer me". Two common mistakes collapse the last state into the first.&lt;br&gt;
Read rejections to the end. The most valuable sentence of the story was in the letter that turned me down.&lt;br&gt;
You may never send email — but to the rest of the internet you ARE a mail server. SMTP has no separate reputation track for "those who only ask".&lt;br&gt;
The verifier from this story runs as an API — Proofmail. Its defining trait was born in investigations exactly like this one: when a mailbox cannot be confirmed, it says "I don't know" instead of guessing.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>smtp</category>
    </item>
  </channel>
</rss>
