The $500/month wake-up call
Three months ago, I checked my SaaS expenses and nearly fell off my chair. NeverBounce was charging me $487 for the month. For email validation.
I run a small project management tool with about 15,000 users. Every signup, I validate their email to make sure it's real. Seems responsible, right? Well, at $0.008 per validation, those pennies add up fast.
The worst part? About 30% of my "validations" were just people mistyping their email on the signup form. I was literally paying money to tell users "you spelled Gmail wrong."
So I did what any developer does when they're annoyed: I spent a weekend building my own solution.
Why email validation matters (the hard way)
Before I was paying NeverBounce, I learned the hard way why you need email validation.
Week 1 without validation:
- 47 signups from
tempmail.comaddresses - 23 from
10minutemail.com - Users creating accounts just to leave spam in comments
- Support tickets from people who couldn't log in (they typo'd their email)
The breaking point:
One user created 150 accounts using mailinator.com addresses. Why? To spam our public roadmap with feature requests for cryptocurrency integration. Fun times.
So yeah, email validation became non-negotiable.
The "just use an API" trap
At first, using NeverBounce seemed smart:
// Simple, right?
const result = await neverBounce.validate('user@example.com');
if (!result.valid) {
return res.status(400).json({ error: 'Invalid email' });
}
It worked! For about three months. Then my user base grew, my bill tripled, and I started looking for alternatives.
Alternatives I tried:
- ZeroBounce - $0.008/validation (same price)
- Hunter.io - Better pricing, but wanted me on annual contract
- Kickbox - Great features, even more expensive
They're all good services. They're just... expensive for a bootstrapped side project.
Building my own (the technical bits)
I figured: how hard can email validation be?
Spoiler: Harder than I thought, but not impossible.
Step 1: Syntax validation (the easy part)
private bool ValidateSyntax(string email)
{
if (string.IsNullOrWhiteSpace(email)) return false;
var parts = email.Split('@');
if (parts.Length != 2) return false;
var local = parts[0];
var domain = parts[1];
// Basic checks
if (local.Length == 0 || local.Length > 64) return false;
if (domain.Length == 0 || domain.Length > 255) return false;
// Regex for the nitty-gritty
var regex = new Regex(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$");
return regex.IsMatch(email);
}
This catches like 80% of garbage right away. Emails without @ signs, multiple @ signs, missing domains, etc.
Step 2: MX record verification (the actually useful part)
This is where things get interesting. You're checking if the domain can actually receive emails.
private async Task<bool> CheckMxRecordsAsync(string domain)
{
try
{
var lookup = new LookupClient();
var result = await lookup.QueryAsync(domain, QueryType.MX);
var mxRecords = result.Answers.MxRecords();
return mxRecords.Any();
}
catch
{
return false;
}
}
This catches:
- Typos like
gmial.com(no MX records) - Made-up domains
- Expired domains
- Domains that can't receive email
It's not perfect (some domains have MX records but still reject mail), but it's pretty good.
Step 3: Disposable email detection (the pain point)
This is where I initially underestimated the problem.
Me: "I'll just hardcode the common ones"
Also me, 2 weeks later: "WHY ARE THERE 10,000 DISPOSABLE EMAIL DOMAINS"
My first attempt:
// Terrible idea, don't do this
private bool IsDisposable(string domain)
{
var bad = new[] { "tempmail.com", "10minutemail.com", "guerrillamail.com" };
return bad.Contains(domain);
}
This caught maybe 20% of disposable emails.
Better approach:
I ended up using the GitHub disposable domains list (https://github.com/disposable/disposable-email-domains). It's maintained by the community and has 10,000+ domains.
private HashSet<string> LoadDisposableDomains()
{
var domains = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var lines = File.ReadAllLines("disposable-domains.txt");
foreach (var line in lines)
{
if (!string.IsNullOrWhiteSpace(line))
domains.Add(line.Trim());
}
return domains;
}
Much better.
Step 4: Typo suggestions (the surprisingly useful feature)
This one came from user feedback. People kept emailing support: "I can't log in!"
Turns out, they typo'd their email on signup. gmial.com instead of gmail.com. Easy mistake.
I built a simple typo checker:
private string SuggestCorrection(string domain)
{
var common = new Dictionary<string, string>
{
{ "gmial.com", "gmail.com" },
{ "gmai.com", "gmail.com" },
{ "yahooo.com", "yahoo.com" },
{ "hotmial.com", "hotmail.com" },
// ... etc
};
return common.GetValueOrDefault(domain);
}
Support tickets dropped by 40% after this. Worth it.
Making it an API (because why not)
Once I had it working for my own app, I figured: might as well make it an API.
Tech stack:
- .NET API (I know, I know, but it's fast)
- Fly.io for hosting (~$3/month)
- Supabase for auth/database
- Stripe for billing
Endpoint:
POST https://api.emailcheck.dev/v1/validate
Content-Type: application/json
X-API-Key: your_key_here
{
"email": "user@gmial.com"
}
Response:
{
"valid": false,
"email": "user@gmial.com",
"syntax_valid": true,
"mx_records_found": false,
"disposable": false,
"suggested_email": "user@gmail.com",
"confidence_score": 20,
"warnings": ["MX records not found", "Did you mean gmail.com?"]
}
The pricing part (where I probably screwed up)
I wanted to make it stupid cheap. Like, "I would have used this instead of NeverBounce" cheap.
My pricing:
- Free: 100/month
- Starter: $9/mo (5,000 validations)
- Pro: $29/mo (50,000 validations)
NeverBounce equivalent:
- 5,000 validations: $40/month
- 50,000 validations: $250/month
Yeah, I'm probably leaving money on the table. But honestly? I built this to solve my own problem. If other people find it useful and I can cover server costs, I'm happy.
What I learned
Things that were easier than expected:
- Basic validation logic
- DNS lookups
- Building the API itself
Things that were harder:
- Disposable email detection (there are SO MANY)
- Edge cases (seriously, email specs are wild)
- Pricing strategy (still not sure I got this right)
Things I'm still working on:
- Catch-all detection (checking if domain accepts everything)
- Email health scoring (is this a good email or sketchy?)
- Better typo detection (using Levenshtein distance)
Try it yourself (the sales pitch, I guess)
If you're dealing with fake signups or paying too much for validation, I built this for you.
Quick integration:
Node.js:
const response = await fetch('https://api.emailcheck.dev/v1/validate', {
method: 'POST',
headers: {
'X-API-Key': 'your_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: 'test@example.com' })
});
const result = await response.json();
if (!result.valid) {
console.log('Invalid email');
}
Python:
import requests
response = requests.post(
'https://api.emailcheck.dev/v1/validate',
headers={'X-API-Key': 'your_key'},
json={'email': 'test@example.com'}
)
result = response.json()
if not result['valid']:
print('Invalid email')
PHP:
$ch = curl_init('https://api.emailcheck.dev/v1/validate');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['email' => 'test@example.com']));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: your_key',
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = json_decode(curl_exec($ch), true);
if (!$result['valid']) {
echo 'Invalid email';
}
The beta program thing
I'm looking for 50 beta testers to stress test this before I do a proper launch. If you sign up now:
- Free Pro plan for 3 months ($29/mo value)
- 50% lifetime discount after beta
- Direct Slack channel with me for support/feature requests
Interested? https://emailcheck.dev/beta
Wrapping up
Look, I'm not saying this will replace NeverBounce or ZeroBounce for everyone. They're great services with way more features than mine.
But if you're a solo dev or small team just trying to keep fake emails out of your database without spending $500/month, I built this for you.
Also, if you have ideas for features or find bugs, hit me up. I'm literally one person running this thing, so feedback is super valuable.
Happy validating!
Tech details:
- API: .NET 10
- Hosting: Fly.io
- Database: Supabase (Postgres)
- Payments: Stripe
- Frontend: Vanilla JS (don't judge me)
Links:
- Website: https://emailcheck.dev
- Docs: https://emailcheck.dev/docs
- Beta signup: https://emailcheck.dev/beta
Tags: #webdev #api #saas #email #validation #indie
Top comments (0)