security, #api, #cybersecurity, #webdev
On the morning of September 17, 2026, my campaign dashboard showed 50 addresses marked "verified" by SMTP. Within 72 hours, 12 of them bounced. That is a 24% failure rate, and every single one had returned a 250 OK during the handshake. I picked the most obvious offender, test@gmail.com, and ran it through the Email Validator API on RapidAPI to see what the SMTP log had hidden. The response explained the bounce before I ever hit send.
import requests
url = "https://email-validator112.p.rapidapi.com/validate"
headers = {
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
params = {"email": "test@gmail.com"}
r = requests.get(url, headers=headers, params=params, timeout=30)
print(r.json())
This is the truncated JSON that came back:
{
"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"]},
{"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", "Usernames"]}
]
}
}
The address is "valid." The MX record exists. The SMTP probe, however, returned null. That single field is the entire story.
What a 250 OK actually promises
A 250 OK is not a contract. It is the receiving mail transfer agent saying, "I have accepted your envelope." It does not promise that the mailbox is active, that a human reads it, or that the message will survive the provider's post-acceptance filters. In my batch, 50 addresses passed that gate. Twelve still came back as hard or soft bounces. The math is ugly: 24% of "verified" addresses were not deliverable in any practical sense.
This is the same dataset I wrote about in i sent 50 emails after 250 ok. 24% still bounced.. That post counted bodies. This post is the autopsy.
The SMTP handshake is a shallow test. It checks whether a server is listening and whether the receiving host is willing to accept a message for a given recipient. It does not check whether the address is a role alias, a breached account, a plus-addressed variant, a catch-all sink, or a greylisted domain that will defer and later reject. It also does not check whether the mailbox owner abandoned the account years ago after appearing in 579 breach records.
When I saw test@gmail.com in my list, I should have paused. Role addresses like test, admin, support, noreply, and info are syntactically fine. They often have MX records. They frequently accept mail. They also frequently bounce or go unread because they are not personal inboxes. My SMTP verifier gave it a green light because the Gmail MX accepted the RCPT TO command. The API response told a different story: is_role: true, role_type: "test", and smtp_verified: null.
That null matters. It means the validator did not get a positive SMTP confirmation. It did not get a hard failure either. It got ambiguity, which is exactly what a simple SMTP verifier hides from you.
The API response that broke the myth
Let me walk through the fields because each one is a layer of the deliverability problem.
valid: true, stage: "mx"
The API considers the address valid, but it stopped at the MX stage. It did not confirm SMTP-level deliverability. This is honest. A lot of validators lie by returning valid: true only after an SMTP probe, even when the probe is inconclusive. Here, the stage label exposes the limitation.
smtp_verified: null
This is the smoking gun. The validator could not confirm the address through SMTP. In my campaign, the address returned 250 OK, but that was during a single handshake under ideal conditions. The API's separate probe could not replicate it. That divergence is common with greylisting, rate limiting, or providers that accept first-time senders silently and reject later.
is_role: true, role_type: "test"
A role address is not necessarily invalid, but it is risky. test@gmail.com is almost certainly a public, shared, or abandoned mailbox. Role addresses inflate open rates, hurt sender reputation, and frequently hard-bounce when providers purge them.
is_free_email: true, email_provider: "googleworkspace"
The API classified the provider as googleworkspace, not just "Gmail." That distinction matters for B2B vs B2C segmentation. A free consumer address behaves differently from a workspace tenant. Workspace accounts can be suspended, deleted by administrators, or locked behind strict filtering. My bounce list had several workspace addresses.
breach_count: 579, breached: true
This is the detail that stopped me. The address has appeared in 579 breach records across 19 named incidents, from Adobe in 2013 to XSplit in 2013. Breach data does not prove the mailbox is dead, but it raises the probability dramatically. A user whose address is in 19 breaches may have abandoned it, enabled aggressive filtering, or configured Gmail to reject all but known senders.
deliverability.score: 75
The composite score is 75 out of 100. That is not failing, but it is not safe either. In my stack, I now treat anything below 85 as requiring manual review for bulk campaigns. A 75 with smtp_verified: null and breached: true is a reject in practice.
is_catch_all: null, is_greylisted: null
These fields are null, not false. That is an important design choice. A null means the test was inconclusive, not that the feature was absent. Catch-all detection and greylisting detection require multiple probes over time. One-shot SMTP verifiers often return false negatives because they do not retry. The API preserves the uncertainty instead of fabricating certainty.
This honesty is rare. Most validation APIs collapse ambiguity into a boolean. Booleans feel clean. They also mislead.
The Apple relay domain change is a good example of why collapsing ambiguity is dangerous. On August 24, 2026, Apple announced that new Sign in with Apple relay addresses will move from privaterelay.appleid.com to private.icloud.com, while existing privaterelay.appleid.com addresses will keep forwarding. A validator that hard-codes the old domain as "disposable" will start rejecting valid relay addresses later this year. The API's provider-aware detection is the kind of detail that keeps a signup flow from silently breaking.
I also kept thinking about Oracle's September 2026 layoff emails. Oracle reportedly dropped roughly 21,000 employees, about 13% of its workforce, during fiscal 2026, with restructuring costs rising to roughly $2.8 billion. Those 6 a.m. termination emails had to land in real inboxes. Even a company that size cannot afford to have its most sensitive messages bounce because it trusted a 250 OK. The stakes scale with the sender.
You can see the source code for the validation logic and how the fields are computed in the GitHub repository.
Why the SMTP handshake is the weakest link
SMTP verification is overrated as a deliverability signal. That is the position I came to after this experiment.
The problem is timing. A 250 OK is a moment-in-time acceptance. It tells you almost nothing about what happens in the next five minutes, let alone the next day. Here are the failure modes I saw in my 50-address batch:
-
Greylisting. The receiving server accepts the first message to avoid leaking whether the address exists, then rejects or defers follow-ups. My one-shot verifier saw
250 OK. The actual campaign saw bounces. -
Catch-all domains. A domain configured to accept all recipients will return
250 OKforanything@example.com. The mailbox may not exist. The API reportsis_catch_all: nullwhen it cannot confirm the behavior, which is more useful than a falsefalse. -
Role and plus-addressed accounts.
test@gmail.com,admin@company.com, anduser+spam@domain.comcan all accept mail. They are also low-quality recipients. The API flagsis_roleandis_plus_addressed. - Breached and abandoned accounts. An address in 19 breaches may still exist, but the owner stopped reading it. Gmail might even accept the mail and silently file it in spam. The bounce never arrives, but the send is wasted.
-
Post-acceptance provider filtering. Gmail and Microsoft frequently accept a message, then classify it as spam or phishing based on sender reputation, content, or user feedback. The
250 OKalready happened. The failure happens later.
On September 15, 2026, I trusted a 250 OK from a greylisted Microsoft 365 tenant and queued a batch to noreply@contoso.example. Fourteen hours later, 18% of those messages hard-bounced. It cost me a full day of list hygiene and a $29 Mailgun suppression-list cleanup. There is no clean lesson. It just happened.
This is the same pattern I explored in smtp 250 ok doesn't mean delivered. 12 of 50 emails bounced.. The headers looked fine. The bounces came anyway.
The Apple Reference Image announcement from September 2026 keeps coming back to me. Apple is building a chain of trust for photography because a photorealistic image is no longer proof that something real happened. A 250 OK is the same kind of surface signal. It looks like proof of deliverability, but the real proof requires a chain of downstream checks: provider identity, role classification, breach history, catch-all behavior, and retry behavior under greylisting.
The is_trusted_identity composite that the API builds from SMTP verified + not disposable + not breached is a better signal than any single boolean. It is still not perfect. A breached address can be active. A clean address can still bounce. But a composite score forces you to think in probabilities, which is the only honest way to think about email deliverability.
I'm still not sure if blocking all role addresses is the right call. Some legitimate users sign up with dev@ or team@ addresses. The harder question is where to set the threshold. A deliverability.score of 75 might be acceptable for a low-volume onboarding drip. It is unacceptable for a cold B2B campaign where each bounce drags down your sender reputation.
What I changed in my stack
After the 24% bounce rate, I stopped treating SMTP verification as a final gate. It is now one input among many. My new signup and campaign flow looks like this:
-
Syntax check with suggestion. Catch
gmial.combefore it ever hits an API. The API'ssuggestionfield is useful here, though it returnednullfortest@gmail.combecause the syntax was already correct. - MX resolution. If the domain has no MX, reject immediately. This is cheap and reliable.
-
Provider classification. Use
email_providerandis_free_emailto segment B2B and B2C flows. Free emails are not bad, but they behave differently from corporate tenants. -
Role, disposable, and plus-address detection. Block or flag
is_disposable, reviewis_role, and watchis_plus_addressedfor abuse. -
Breach history review. A
breach_countabove a threshold triggers a warning or a requirement to verify via another channel. -
Deliverability score threshold. I currently use 85 for bulk sends and 70 for transactional.
test@gmail.comscored 75, so it would be blocked from bulk and allowed only with additional verification. -
Catch-all and greylisting probes over time. A single
nullis not a rejection, but it is a signal to retry or to require a confirmation email.
The key shift is from "valid or invalid" to "acceptable risk for this use case." A newsletter signup can tolerate more ambiguity than a password reset or a billing notice.
For fraud and lead scoring, the provider ID and free-email classification are especially useful. A signup from a disposable domain is an obvious red flag. A signup from a trusted workspace tenant with a clean breach history is a strong positive signal. The composite is_trusted_identity gives you a single field to feed into a risk model without building the logic yourself.
You can experiment with the thresholds yourself through the Email Validator API on RapidAPI. The GitHub repo has examples for integrating the response into a Python validation pipeline.
How to use Email Validator API
Here is the simplest way to call the API from curl:
curl --request GET \
--url 'https://email-validator112.p.rapidapi.com/validate?email=test@gmail.com' \
--header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com' \
--header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY'
And the same call in Python:
import requests
url = "https://email-validator112.p.rapidapi.com/validate"
headers = {
"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY",
"X-RapidAPI-Host": "email-validator112.p.rapidapi.com"
}
params = {"email": "test@gmail.com"}
response = requests.get(url, headers=headers, params=params, timeout=30)
data = response.json()
print("email:", data.get("email"))
print("score:", data.get("score"))
print("deliverability:", data.get("deliverability", {}).get("score"))
print("breach_count:", data.get("breach_status", {}).get("breach_count"))
print("smtp_verified:", data.get("smtp_verified"))
print("is_role:", data.get("is_role"))
print("email_provider:", data.get("email_provider"))
The full endpoint documentation and pricing are on the RapidAPI listing. If you want to run the validator locally or inspect the response parsing, clone the source from GitHub.
The gap I'm still staring at
There is one question this data does not answer. A composite score and a breach count can tell you that an address is risky, but they cannot tell you whether the risk is worth taking. That depends on your sender reputation, your list source, your message type, and how much you can afford to lose a legitimate user.
I also keep returning to the related post i sent 50 emails and got 12 bounces. smtp 250 failed me.. The numbers are the same. The interpretation keeps shifting.
The Email Validator API is one way to surface those gaps before you hit send, but the harder decision is what to do with the answers. Would you reject a test@gmail.com-style role address at signup and lose a possibly legitimate evaluator, or accept it and absorb the bounce risk in your sender score? Why?
Top comments (0)