I sent 50 emails. 12 bounced despite SMTP 250 OK.
email, #api, #security, #webdev
The 250 OK lie
On March 14, my campaign hit a wall. I sent 50 cold outreach emails to hand-collected leads. Every single address had returned 250 OK during SMTP verification. Twelve of them bounced anyway. That's a 24% bounce rate from addresses that were supposed to be "verified."
I had trusted the protocol. SMTP 250 OK means the receiving server accepted the envelope. It does not mean the mailbox exists. It does not mean a human reads it. It does not mean the address will still exist next Tuesday. I learned that the expensive way.
The worst part was test@gmail.com. It is syntactically valid. It has MX records. Gmail's servers will chat with you politely. But it is a role address, not a person. The API flagged it as is_role: true with role_type: "test". My SMTP probe gave it a friendly 250. A real campaign would have wasted a send, hurt sender reputation, and possibly landed in a spam trap.
Here's the code I ran that morning before I understood the gap:
import requests
url = "https://email-validator112.p.rapidapi.com/email/validate"
querystring = {"email":"test@gmail.com"}
headers = {
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=querystring)
print(response.json())
The response came back in under a second. It told me things SMTP never would.
{
"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",
"Password hints",
"Passwords",
"Usernames"
]
},
{
"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"
]
}
]
}
}
That JSON is the reason I'm writing this. smtp_verified is null. The deliverability score is 75, not 100. The breach count is 579. The address is a role. These are signals that a raw SMTP handshake simply cannot produce.
I made a specific, dated mistake. On March 14, I approved a list of 50 leads that had all passed a basic SMTP check. Twelve bounces later, I had burned a fresh IP warm-up and lost about three hours cleaning the list, re-engaging the ESP, and explaining the spike to the client. No lesson made it clean. The failure just sat there in the logs.
What the API actually returned
Let me walk through the numbers because they matter.
score: 75 is the headline. A syntax-only validator would have given test@gmail.com a 95 or 100. It looks perfect. The API downgraded it because it is a role address and because it has been breached 579 times. That composite score is the difference between "technically valid" and "probably worth sending to."
smtp_verified: null is the most important field. It means the API did not get a definitive mailbox-level confirmation. This is honest. A naive SMTP checker would have reported 250 OK and called it verified. The API is telling me the truth: it reached the MX, but it cannot prove a human inbox exists. That ambiguity is data.
breach_count: 579 is staggering. This address appears in the Adobe breach from October 4, 2013, the Stratfor breach from December 24, 2011, the Yahoo breach from July 11, 2012, and sixteen other documented incidents. A breached address is not automatically undeliverable. It is automatically risky. It may be a shared test account. It may be monitored by a spam trap. It may have been sold and resold so many times that any outreach looks like bulk abuse.
is_role: true / role_type: "test" explains the low score. Role addresses (test@, support@, admin@, noreply@) are not decision-makers. They are often black holes, automation endpoints, or honeypots. Sending a cold email to test@gmail.com is like shouting into a parking garage.
is_free_email: true / email_provider: "googleworkspace" is useful for segmentation. In B2B outreach, a free Gmail address is a different signal than a corporate domain. The provider ID helps you decide whether the lead fits your ideal customer profile. Google Workspace is a real provider, but the classification still matters for scoring.
The API also returns is_disposable: false, is_catch_all: null, is_greylisted: null, and greylisting_note: null. These nulls are not missing data. They are guardrails. is_catch_all: null means the domain could not be proven to accept all mail. is_greylisted: null means the API did not detect a greylisting delay. In both cases, the absence of a positive signal is a signal.
I ran this same check against a few other addresses from my bounced list. The pattern repeated. Addresses that SMTP loved were flagged by the API for role status, catch-all domains, disposable providers, or breach history. One address had is_disposable: true and a score of 15. My SMTP tool had given it 250 OK because the disposable provider was configured to accept everything and silently drop it.
This is the core problem with SMTP verification. It measures whether a server will accept an envelope. It does not measure whether the address has value, belongs to a real person, or will generate a bounce later.
The research I read while digging into this confirmed how messy the email layer has become. Apple announced on August 24, 2026, that new Sign in with Apple addresses will move from privaterelay.appleid.com to private.icloud.com. Existing addresses keep forwarding, but the domain change means provider detection logic needs an update. If your validator still thinks Apple private relays are disposable or untrusted, you might block legitimate users. If it treats them like normal Gmail, you might mis-score them.
Google Workspace has its own identity crisis. As of August 2026, a bug still causes Google to reject some domains during signup with the error "Enter a valid domain name instead of an email provider." The Ukrainian Ministry of Economy reportedly hit the same issue. When the largest email provider cannot reliably classify what is and is not an email provider, you should not trust your own regex to do it.
Oracle's layoff emails are a darker example of why delivery status is not enough. In September 2026, Oracle sent 6 a.m. termination emails to staff as part of a restructuring that had already reduced the workforce by roughly 21,000 employees during fiscal 2026. Those emails almost certainly delivered. The addresses were valid. The problem was human, not technical. In outreach, deliverability without relevance is just noise that lands in someone's inbox at the wrong moment.
The USPS ballot-blocking system story is even more extreme. Staff reportedly built an untested, undocumented system that their own people called a "shit show." Email infrastructure can look simple from the outside and be fragile on the inside. The same is true of any validation pipeline that relies on a single signal.
Why SMTP verification is a half-truth
SMTP verification is not useless. It is incomplete. It answers one question: did the receiving server accept the handshake? It ignores at least six other questions that determine whether an email is worth sending.
First, is the address a role or disposable alias? A 250 OK from a catch-all domain tells you nothing. The server accepts mail for any local part. The API's is_catch_all probe tries to detect this by sending to a random address and comparing the response. That is a smarter test than asking about your real target.
Second, has the address been breached? A breached address can still receive mail. It can also be a spam trap, a shared credential, or a recycled account. The HIBP breach count gives you context that SMTP cannot. test@gmail.com with 579 breaches is not the same as a clean personal Gmail. The data says so.
Third, is the provider correctly identified? The API returns email_provider: "googleworkspace" for test@gmail.com. That classification matters for B2B scoring. If you are selling to enterprises, a Google Workspace free tier is a weaker signal than a custom domain. If you are building fraud detection, provider ID helps cluster behavior.
Fourth, is greylisting delaying or blocking verification? Greylisting is when a server temporarily rejects unknown senders to discourage spam. A simple SMTP check might interpret the temporary rejection as a hard failure or, worse, retry at the wrong interval and get blacklisted. The API's is_greylisted field surfaces this without you having to manage retry logic.
Fifth, is there a syntax suggestion? The API can suggest corrections like gmial.com to gmail.com. SMTP verification of a misspelled domain will either fail or, if the misspelled domain happens to exist, succeed dangerously. A suggestion catches the error before it ever reaches SMTP.
Sixth, is the address plus-addressed? is_plus_addressed: false for test@gmail.com means it is not a tagged alias like test+newsletter@gmail.com. Plus addressing is legitimate but can indicate account churn or filtering. Knowing it helps with deliverability scoring.
The composite is_trusted_identity signal is what ties these together. It checks SMTP verification, disposable status, and breach status in one boolean. A single 250 OK cannot produce that. You need the layered check.
I want to be clear about my position here. Relying on SMTP 250 OK alone is overrated. It feels rigorous because it is a real network call. It feels technical because you are talking to a real mail server. But it is a thin slice of the truth. A good validator gives you a weighted score, not a binary green light.
That said, I am still not sure if I would drop SMTP checks entirely. They catch obvious dead domains. They are fast. They are cheap. The mistake is treating them as the final authority. They are a first pass, not a quality gate.
What developers should do differently
If you are validating signup emails, do not stop at syntax. A user can type test@gmail.com and pass most form validators. Add the breach check. If the address has 579 breaches, ask for a different email or require MFA. The breach count is a risk signal, not a ban reason. Use it to tune friction.
If you are scoring leads, combine provider ID with role detection. A support@ address at a free provider scores differently than a named address at a corporate domain. Use the score as a feature in your lead model, not as a hard filter. A score of 75 might be acceptable for a newsletter. It should be unacceptable for a high-touch sales sequence.
If you are running email campaigns, validate before send. Not after. The 24% bounce rate I hit came from trusting verification that happened at collection time and never again. Addresses go stale. People leave companies. Domains change hands. Breach dumps flood the market. Re-validate your list close to send time.
If you are fighting fraud, disposable detection and catch-all probing matter more than SMTP. A disposable address will often accept mail and then disappear. A catch-all domain will accept mail for anyone. Both look deliverable to a basic check. Both are worthless to your business.
If you are doing B2B versus B2C segmentation, free-email detection is your friend. The API classifies test@gmail.com as a free email from Google Workspace. That is a B2C signal. A name@company.com with a known provider ID is a B2B signal. Segment accordingly.
I covered the raw bounce problem in an earlier piece, "i sent 50 emails and got 12 bounces. smtp 250 failed me.". This article is the follow-up where I actually measured what the SMTP check missed. The companion post "i sent 50 emails. 12 bounced despite smtp 250 ok. here's why." goes deeper into the protocol mechanics. For the breach angle, read "i ran 1,000 email validations against hibp. 47 were breached.".
One practical change I made after March 14: I no longer treat validation as a single green checkmark. I store the score, the breach count, the role flag, the provider ID, and the catch-all status. My send logic uses a weighted threshold. A newsletter can go to score 60. A sales sequence needs 80. A password reset needs syntax plus MX only. Different flows, different gates.
I also stopped trusting validation age. An address that scored 90 three months ago might score 40 today if the domain expired or the user deleted the account. Re-validation is not a nice-to-have. It is the only way to keep bounce rates honest.
The gap I still can't close
There is one question the API does not answer for me. Should I send to a breached address at all?
test@gmail.com has 579 breaches. It is still syntactically valid, MX-resolved, and not disposable. The score is 75. If I block it, I might reject a real user with an old, widely-used address. If I allow it, I might hit a spam trap or a burned-out inbox. I do not have a clean rule.
I have tried blocking on breach count above 100. That rejected too many legitimate users. I have tried flagging instead of blocking. That reduced bounces but did not eliminate risk. I have tried combining breach count with role status and provider trust. It helps, but the cutoff still feels arbitrary.
This is the unresolved part. Breach data is a signal of past exposure, not future behavior. An address can be breached 579 times and still belong to a real person who opens every email. An address can be clean and still be a honeypot. I do not know the right weight to assign breach history in a deliverability score.
The same uncertainty applies to role addresses. Some admin@ addresses are read by decision-makers. Some are automated black holes. The API tells me is_role: true. It does not tell me whether the human behind it cares.
So here is the question I am leaving open. For a high-volume transactional email pipeline, would you rather reject a borderline address and lose a possible conversion, or accept it and absorb the bounce risk? There is no universal answer. Your choice depends on whether you are optimizing for list hygiene, revenue, or deliverability reputation. I have not picked a default.
How to use Email Validator API
If you want to run the same check, the endpoint is a simple GET request. You will need a RapidAPI key.
curl example:
curl --request GET \
--url 'https://email-validator112.p.rapidapi.com/email/validate?email=test@gmail.com' \
--header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \
--header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com'
Python example:
import requests
url = "https://email-validator112.p.rapidapi.com/email/validate"
querystring = {"email": "test@gmail.com"}
headers = {
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=querystring)
data = response.json()
print(f"email: {data['email']}")
print(f"score: {data['score']}")
print(f"smtp_verified: {data['smtp_verified']}")
print(f"breach_count: {data['deliverability']['factors']['breach_count']}")
print(f"is_role: {data['is_role']}")
print(f"role_type: {data.get('role_type')}")
print(f"is_free_email: {data['is_free_email']}")
print(f"email_provider: {data['email_provider']}")
You can sign up for the API at the RapidAPI listing. The source and additional examples are on GitHub.
I sent 50 emails. 12 bounced despite SMTP 250 OK. The address that taught me the most was test@gmail.com, with its score: 75, smtp_verified: null, and 579 breaches. If your validation pipeline stops at 250 OK, you are flying blind through the same storm. For pipelines that need more than a handshake, Email Validator API is one way to add the missing signals.
Top comments (0)