DEV Community

J Kelly
J Kelly

Posted on

How I Stopped Worrying About Fake Signups (And Built an API Instead)

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.com addresses
  • 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' });
}
Enter fullscreen mode Exit fullscreen mode

It worked! For about three months. Then my user base grew, my bill tripled, and I started looking for alternatives.

Alternatives I tried:

  1. ZeroBounce - $0.008/validation (same price)
  2. Hunter.io - Better pricing, but wanted me on annual contract
  3. 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);
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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?"]
}
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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';
}
Enter fullscreen mode Exit fullscreen mode

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:


Tags: #webdev #api #saas #email #validation #indie


Top comments (0)