Why 12% of Our Signups Were Fake — and What We Did About It
Source: Dev.to
Last October, I opened our ESP dashboard and saw 12.3 % hard bounces on onboarding emails.
Not soft bounces. Not “mailbox full.” Hard 550 rejections – the kind that make your provider send you a warning email that feels like a threat.
We weren’t spamming anyone. We weren’t scraping lists. These were users who had just signed up.
That’s when it clicked. This wasn’t a sending problem. It was a signup problem.
What’s Actually Happening When an Email Bounces
Most developers don’t look at the SMTP layer until something breaks. I didn’t either.
When your server sends mail, it connects to the recipient domain’s MX record. The flow usually looks like this:
1. TCP connect
2. EHLO your-domain.com
3. MAIL FROM:
4. RCPT TO:
If the mailbox doesn’t exist, the remote server replies with something like:
550 5.1.1 User unknown
That rejection often happens before any message body is sent – no content, no HTML, just a protocol‑level “no.”
If that happens often enough, your sending IP starts to look suspicious. Mail providers track this closely. A high hard‑bounce rate tells them you don’t control your data, and they don’t like that.
Why Regex and Double Opt‑In Didn’t Save Us
Our first move was predictable: regex validation.
/^[^\s@]+@[^\s@]+\.[^\s@]+$/
It catches obvious garbage, but it does nothing for fakeuser@gmail.com.
Next we added an MX check. If a domain had no mail server, we rejected it. That removed some trash domains, but it still allowed addresses that didn’t exist on real domains.
We leaned on double opt‑in next. Double opt‑in helps, but it’s reactive. You still send that first message. If it bounces, the damage is already done. Your reputation drops before the user ever clicks anything.
We needed to stop bad addresses before they touched our database.
The Fix: Real‑Time Mailbox Checks at Signup
We decided to verify addresses at the moment of signup – not just format validation, not just domain existence, but an actual mailbox check.
We ended up using VerifiSaaS (https://verifisaas.com). There are other tools out there like ZeroBounce and NeverBounce, but we wanted something API‑first that fit directly into our backend without weird CSV workflows.
Here’s what our Next.js API route looks like now:
// pages/api/signup.js
export default async function handler(req, res) {
const { email, password } = req.body;
const response = await fetch('https://api.verifisaas.com/v1/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.VERIFISAAS_API_KEY}`
},
body: JSON.stringify({ email })
});
const result = await response.json();
const { status, confidence_score, classification } = result;
if (status === 'undeliverable') {
return res.status(400).json({ error: 'That email address does not exist.' });
}
if (confidence_score < 0.6 || classification === 'risky') {
return res.status(400).json({ error: 'Please use a valid email address.' });
}
return res.status(200).json({ success: true });
}
That’s it. The form posts to this route, we verify before writing anything to the database. If the address fails, we stop right there – no welcome email, no bounce, no reputation hit.
What Changed After We Shipped It
- Within a week, our hard‑bounce rate dropped from ≈12 % to <0.5 %.
- That’s roughly one in ten emails failing down to about four per thousand.
- Our ESP stopped sending warning emails.
- Our sender score climbed slowly over the next couple of weeks.
- Support tickets about missing onboarding emails dropped.
The bigger win wasn’t just reputation.
- Our database got cleaner.
- Marketing automation stopped choking on junk accounts.
- Analytics became more trustworthy.
- We weren’t building features on top of fake users anymore.
This should have been in v1.
The Parts That Get Messy
It’s not all clean 250 or 550 responses.
- Catch‑all domains accept any address during the SMTP check, then silently drop mail later. If you treat every
250as valid, you’ll overestimate quality. We don’t auto‑block catch‑alls; we flag them. For some users that’s fine, for high‑risk signups we ask for additional confirmation. - Disposable providers are another grey area. For B2B SaaS they’re usually low intent; for consumer apps they can be legitimate privacy choices. We flag them and decide based on context instead of blocking blindly.
- Performance – if your signup form gets traffic spikes and you verify every address in real time, you’ll feel it. We added basic rate limiting and a fallback mode if the verification API times out. The signup flow can’t depend on a single external call without guardrails.
When This Is Worth It
- If you’re sending a handful of emails a week, this probably doesn’t matter.
- If you’re running paid acquisition, onboarding thousands of users, or sending transactional mail tied to revenue, you can’t afford double‑digit bounce rates.
We learned that the hard way. Fixing deliverability after your domain reputation drops is painful. Preventing bad data at the edge is much easier.
What I’d Do Differently
I would have treated signup validation as part of email infrastructure from day one.
We thought email issues lived in the sending layer. They didn’t. They lived in the input layer.
Bad data spreads fast. It contaminates analytics, marketing, billing, and support workflows. Once it’s in, cleaning it up is a nightmare.
Happy coding, and may your bounce rate stay under 0.5 %!