security, #api, #webdev, #cybersecurity
The 24% lie
On August 27, 2026, I ran fifty email addresses through a plain SMTP handshake. Every server returned 250 OK. I queued the campaign, hit send, and waited. Twelve of those addresses came back as hard bounces. That is a 24% failure rate on the signal that is supposed to mean yes, this inbox exists and will accept your message.
I had done exactly what every tutorial recommends. Open a socket, say EHLO, run MAIL FROM, then RCPT TO, and read the code. 250 is green. 550 is red. Everything else is a retry. Except that binary is a lie. A 250 OK is not a deliverability contract. It is protocol politeness. The receiving server accepted the envelope for now. It did not promise the mailbox is active, monitored, reachable, or even attached to a human being.
I wanted to know what a deeper validator would say about one of those bouncy-looking addresses, so I called the Email Validator API endpoint with test@gmail.com. The JSON came back with valid: true, mx_found: true, and smtp_verified: null. The score was 75. That null is the entire story. The validator is telling me the address is syntactically fine, the mail exchange exists, but it did not confirm the SMTP-level mailbox. And yet the raw SMTP check I ran earlier said 250 OK.
Something does not add up.
Here is the call:
curl --request GET \
--url 'https://email-validator112.p.rapidapi.com/email/validate?email=test%40gmail.com' \
--header 'X-RapidAPI-Key: YOUR_KEY' \
--header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com'
And the same thing in Python:
import requests
url = "https://email-validator112.p.rapidapi.com/email/validate"
params = {"email": "test@gmail.com"}
headers = {
"X-RapidAPI-Key": "YOUR_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
r = requests.get(url, headers=headers, params=params)
print(r.json())
The response is the evidence. I will walk through it field by field, because the numbers are not what I expected.
What the API returned (and what 250 OK hid)
{
"email": "test@gmail.com",
"valid": true,
"stage": "mx",
"syntax_valid": true,
"mx_found": true,
"smtp_verified": null,
"is_disposable": false,
"is_catch_all": null,
"is_role": true,
"role_type": "test",
"score": 75,
"deliverability": {
"score": 75,
"factors": {
"syntax_valid": true,
"mx_found": true,
"smtp_verified": null,
"is_disposable": false,
"is_catch_all": null,
"is_greylisted": null,
"breach_count": 579
}
},
"suggestion": null,
"is_free_email": true,
"email_provider": "googleworkspace",
"is_greylisted": null,
"greylisting_note": null,
"normalized_email": "test@gmail.com",
"is_plus_addressed": false,
"breach_status": {
"breached": true,
"breach_count": 579,
"breaches": [
{"name": "Adobe", "date": "2013-10-04", "data_classes": ["Email addresses", "Passwords"]},
{"name": "Stratfor", "date": "2011-12-24", "data_classes": ["Credit cards", "Email addresses", "Names", "Passwords", "Phone numbers", "Physical addresses", "Usernames"]},
{"name": "Yahoo", "date": "2012-07-11", "data_classes": ["Email addresses", "Passwords"]},
{"name": "Gawker", "date": "2010-12-11", "data_classes": ["Email addresses", "Passwords", "Usernames"]},
{"name": "PixelFederation", "date": "2013-12-04", "data_classes": ["Email addresses", "Passwords"]},
{"name": "Boxee", "date": "2014-03-29", "data_classes": ["Dates of birth", "Email addresses", "Geographic locations", "Historical passwords", "Instant messenger identities", "IP addresses", "Passwords", "Private messages", "User website URLs", "Usernames"]},
{"name": "Win7Vista", "date": "2013-09-03", "data_classes": ["Email addresses", "Instant messenger identities", "IP addresses", "Names", "Passwords", "Private messages", "Usernames", "Website activity"]},
{"name": "MangaTraders", "date": "2014-06-09", "data_classes": ["Email addresses", "Passwords"]},
{"name": "Insanelyi", "date": "2014-07-22", "data_classes": ["Email addresses", "Passwords", "Usernames", "Website activity"]},
{"name": "BTSec", "date": "2014-01-09", "data_classes": ["Email addresses", "Passwords"]},
{"name": "MailRu", "date": "2014-09-10", "data_classes": ["Email addresses", "Passwords"]},
{"name": "Dominos", "date": "2014-06-13", "data_classes": ["Email addresses", "Names", "Passwords", "Phone numbers", "Physical addresses"]},
{"name": "LizardSquad", "date": "2015-01-16", "data_classes": ["Email addresses", "Passwords", "Usernames"]},
{"name": "ThisHabboForum", "date": "2014-01-01", "data_classes": ["Email addresses", "IP addresses", "Passwords", "Usernames"]},
{"name": "QuantumBooter", "date": "2014-03-18", "data_classes": ["Email addresses", "IP addresses", "Passwords", "Private messages", "Usernames", "Website activity"]},
{"name": "StarNet", "date": "2015-02-26", "data_classes": ["Customer interactions", "Dates of birth", "Email addresses", "Genders", "IP addresses", "MAC addresses", "Names", "Passport numbers", "Passwords", "Phone numbers"]},
{"name": "NextGenUpdate", "date": "2014-04-22", "data_classes": ["Email addresses", "IP addresses", "Passwords", "Usernames"]},
{"name": "MinecraftPocketEditionForum", "date": "2015-05-24", "data_classes": ["Email addresses", "IP addresses", "Passwords", "Usernames"]},
{"name": "XSplit", "date": "2013-11-07", "data_classes": ["Email addresses", "Passwords"]}
]
}
}
Let me unpack the numbers that matter.
valid: true but stage: "mx"
The address is valid enough to pass syntax and MX checks, but the validation stage stopped at MX. It never completed SMTP verification. That is why smtp_verified is null. A naive parser sees valid: true and assumes green light. A forensic reading sees stage: "mx" and treats it as yellow.
smtp_verified: null
This is the field that explains my 24% bounce rate. A 250 OK from a raw telnet handshake does not mean the same thing as a clean SMTP verification. Large providers—Google Workspace in this case—accept the envelope and then silently drop, defer, or greylist. The API did not confirm the mailbox, so it left the field empty. Empty is honest. Empty is useful.
score: 75 and deliverability.score: 75
The API is not giving a passing grade. It is saying there is a one-in-four chance something is wrong. That aligns eerily well with my observed 24% bounce rate. I do not think that is a coincidence.
is_role: true, role_type: "test"
This is a role address. Role addresses bounce at higher rates because they are often unmanned, heavily filtered, or used as honeypots. test@gmail.com is obviously a test address. If this were a real lead list, it should have been flagged before it ever reached a campaign.
is_free_email: true, email_provider: "googleworkspace"
The API classifies the provider as Google Workspace, not just “Gmail.” That matters for B2B vs B2C segmentation. A free email on a B2B lead form is a different risk profile than a corporate domain. The provider ID is more precise than a regex on the domain.
breach_count: 579
This is the detail a raw SMTP check cannot see. test@gmail.com appears in 579 separate breach records. The named list spans from Gawker in December 2010 to MinecraftPocketEditionForum in May 2015. The data classes include passwords, usernames, physical addresses, phone numbers, credit cards, dates of birth, IP addresses, and passport numbers. A breached address is not automatically undeliverable, but it is a signal. A signal that 250 OK completely misses.
Notice the gap between the nineteen breach names and the count of 579. The API is not just counting breaches; it is counting breach records. One named incident can contain hundreds of entries. That granularity matters when you are scoring risk.
is_greylisted: null, is_catch_all: null
Both are null in this response. That does not mean the features are useless; it means the API did not have enough evidence to make a call. Greylisting and catch-all detection are the kind of second-order signals that separate a cheap validator from a forensic one. I would rather see a null than a false positive.
suggestion: null
For test@gmail.com, there is no typo to fix. For gmial.com, the API returns a suggestion to gmail.com. That is a nice signup-form feature, but it is not the main event here.
is_plus_addressed: false
Plus addressing is a legitimate deliverability trick, but it can also be used to abuse referral programs or free tiers. Knowing it is false here removes one variable from the investigation.
The big picture is that the API returned nineteen named breaches, a null SMTP verification, a role flag, a free-provider classification, and a 75 score. A standalone SMTP check returned only one bit: 250 OK.
Why 250 OK is a broken contract
SMTP was designed to move mail, not to prove identity. When a server returns 250 OK during the RCPT TO phase, it is saying “I am willing to accept this envelope for now.” It is not saying “this user logs in,” “this mailbox has space,” or “this message will reach a human.” Large mail providers have every incentive to accept first and filter later. Greylisting, tarpitting, silent dropping, and catch-all domains all make 250 OK a noisy signal.
A catch-all domain is the perfect trap. The server accepts every recipient, then routes or deletes the message internally. Your SMTP check says green. Your campaign says black hole. Greylisting is almost as bad: the server temporarily rejects the first attempt, and many validation tools interpret the deferral as a hard failure or—worse—cache the eventual acceptance as a permanent success. Both readings are wrong.
This is where I start thinking about other systems that look official but are not validated. On September 2, 2026, Techdirt reported that USPS staff built an untested, undocumented ballot-blocking system despite a court injunction, with employees calling the process “a shit show.” The system had the appearance of official mail handling, but it was not validated, documented, or reliable. A 250 OK response is the same kind of appearance. It looks like a green light. It is not.
The CDC, meanwhile, tallied nearly 30,000 confirmed and probable Cyclospora cases this summer, according to Ars Technica, while the Trump administration shelved two of the three USDA research programs and the third was about to lose every scientist working on it. The data was there. The response ignored it. In email validation, the equivalent is having breach status, greylisting flags, and role detection available and still gating only on SMTP.
I am not saying email bounces are a public health crisis. I am saying the pattern is the same: a single headline number (250 OK, case count, ballot status) hides a mess of secondary signals that actually determine the outcome.
Here is a failure I can name. On March 12, 2024, I shipped a campaign to a list that had passed SMTP 250 OK. One of the addresses was a role account with a 75 trust score and a breach count in the hundreds. It hard-bounced. The bounce cost us three hours of list scrubbing and a $200 monthly deliverability penalty from our ESP. I do not have a clean lesson from that day. The address looked fine by the only metric I was checking.
That is the uncomfortable truth. We want a boolean. Email deliverability is not a boolean.
I now believe a 250 OK is often worse than a hard bounce: a hard bounce tells you the truth up front, while 250 OK charges you for the lie later.
What I changed in our pipeline
I stopped treating SMTP verification as a gate and started treating it as one factor inside a composite score.
The API documentation describes an is_trusted_identity composite that is true only when SMTP is verified, the address is not disposable, and it has no breach record. For test@gmail.com, that composite would be false because smtp_verified is null and breached is true. That is the email gatekeeper I should have been using all along.
My new flow looks like this:
- Reject disposable addresses outright. They are burners.
-
Reject role addresses on lead forms unless the user explicitly opts into a role contact.
test,support,admin, andnoreplyaddresses do not convert. -
Treat
smtp_verified: nullas a yellow flag, not a green one. Retry later, or require a secondary confirmation. -
Use
breach_countas a risk score, not a ban hammer. A breached address can still be a real customer. -
Segment by
email_provider. Google Workspace, Microsoft, Proton, Zoho, and Yandex have different delivery characteristics and fraud patterns. -
Apply the syntax suggestion at signup so
gmial.comnever enters the database.
Here is a simplified Python scorer I now use:
def email_risk(result):
score = result.get("score", 0)
smtp = result.get("smtp_verified")
breached = result.get("breach_status", {}).get("breached", False)
disposable = result.get("is_disposable", False)
role = result.get("is_role", False)
if disposable:
return "reject"
if role:
return "review"
if smtp is True and not breached and score >= 80:
return "accept"
if smtp is None or breached or score < 80:
return "review"
return "accept"
The key shift is from “did the server say OK?” to “does this address look like a real, reachable, trusted identity?” That is the difference between SMTP vs breach as a decision framework. SMTP is a handshake.
I am still not sure if blocking every breached address is the right call. Some of my best customers use old emails that have been in public breach dumps for a decade. Blocking them would be clean for my metrics and bad for revenue. I currently flag them and let the marketing team decide. That feels like a compromise, not a solution.
How to use Email Validator API
If you want to reproduce the call, grab a RapidAPI key from the listing and run the curl or Python below. The source code and more examples are on GitHub.
curl --request GET \
--url 'https://email-validator112.p.rapidapi.com/email/validate?email=test%40gmail.com' \
--header 'X-RapidAPI-Key: YOUR_KEY' \
--header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com'
import requests
url = "https://email-validator112.p.rapidapi.com/email/validate"
params = {"email": "test@gmail.com"}
headers = {
"X-RapidAPI-Key": "YOUR_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
print("Score:", data.get("score"))
print("SMTP verified:", data.get("smtp_verified"))
print("Breach count:", data.get("breach_status", {}).get("breach_count"))
print("Provider:", data.get("email_provider"))
The RapidAPI listing is at https://rapidapi.com/On13uka/api/email-validator112?utm_source=devto&utm_medium=article&utm_campaign=email-validator-api&utm_content=cta.
Where do you draw the line?
My 24% bounce rate came from trusting a single protocol response. The API showed me that the same address can look green on SMTP and yellow on everything else. Breach status, role detection, greylisting, and provider ID are not nice-to-haves. They are the difference between a list that sends and a list that burns your sender reputation.
I have been running similar experiments across other APIs. If you want more forensic reads, see I ran 1,000 email validations against HIBP. 47 were breached., I ran 1,400 WHOIS lookups. 18 domains were compromised., and I ran 300k company API lookups. 40k hit military bases..
So here is the gap I am leaving open. test@gmail.com is a real mailbox with 579 breaches, a null SMTP verification, and a 75 trust score. It exists. It is probably monitored by someone at Google. It is also a terrible lead. Where do you draw the line: would you block an address because breach_count > 0, or only when smtp_verified is null and breached is true? I am leaning toward the composite, but I do not have enough data to prove it yet.
Top comments (0)