DEV Community

Rahul S
Rahul S

Posted on

Catch Disposable Emails Before They Wreck Your SaaS Metrics

Every SaaS founder knows the pain: your free trial signups look great on paper, but half of them are throwaway accounts using disposable emails. They inflate your metrics, abuse free tiers, and never convert.

Let's fix that — with code.

The Disposable Email Problem

Services like Guerrilla Mail, Temp Mail, and Mailinator let anyone generate a throwaway email in seconds. For SaaS products with free tiers, this means:

  • Metric pollution — your signup numbers lie
  • Free tier abuse — the same person creates 10 accounts
  • Wasted onboarding emails — bouncing into the void
  • Skewed cohort analysis — your retention data is garbage

Traditional regex-based blocklists can't keep up. New disposable domains pop up daily. You need a real-time intelligence layer.

Beyond Blocklists: Real-Time Email Intelligence

The smarter approach combines multiple signals:

  1. Domain age — disposable domains are often days or weeks old
  2. MX record analysis — throwaway services have recognizable mail server patterns
  3. Known disposable provider databases — continuously updated
  4. IP reputation correlation — is the signup IP also suspicious?

Checking all four gives you a confidence score, not just a binary yes/no.

Implementation: Node.js Example

Here's a practical signup validation middleware using IPASIS, which bundles IP + email intelligence in a single API call:

// middleware/validateSignup.js
const IPASIS_API = 'https://ipasis.com/api/v1';

async function validateSignup(req, res, next) {
  const { email } = req.body;
  const ip = req.headers['x-forwarded-for'] || req.ip;

  try {
    // Single API call checks both IP reputation + email validity
    const response = await fetch(
      `${IPASIS_API}/scan?ip=${ip}&email=${encodeURIComponent(email)}`,
      { headers: { 'Authorization': `Bearer ${process.env.IPASIS_KEY}` } }
    );
    const data = await response.json();

    // Check email signals
    if (data.email?.disposable) {
      return res.status(400).json({
        error: 'Please use a permanent email address'
      });
    }

    // Check IP signals — VPN + Tor + known bot
    if (data.ip?.threat_score > 75) {
      // Log for review but don't hard-block
      req.flagged = true;
      console.warn(`High-risk signup: ${email} from ${ip}`, {
        vpn: data.ip.vpn,
        tor: data.ip.tor,
        bot: data.ip.bot,
        threat_score: data.ip.threat_score
      });
    }

    req.ipasis = data;
    next();
  } catch (err) {
    // Fail open — don't block signups if the API is unreachable
    console.error('IPASIS check failed:', err.message);
    next();
  }
}

module.exports = validateSignup;
Enter fullscreen mode Exit fullscreen mode

Using it in Express:

const express = require('express');
const validateSignup = require('./middleware/validateSignup');

const app = express();
app.use(express.json());

app.post('/api/signup', validateSignup, async (req, res) => {
  if (req.flagged) {
    // Route to manual review queue instead of auto-activating
    await addToReviewQueue(req.body, req.ipasis);
    return res.json({ message: 'Account pending review' });
  }

  // Clean signup — proceed normally
  await createUser(req.body);
  res.json({ message: 'Welcome aboard!' });
});
Enter fullscreen mode Exit fullscreen mode

Python Flask Version

import requests
from functools import wraps
from flask import request, jsonify

IPASIS_API = "https://ipasis.com/api/v1"

def validate_signup(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        email = request.json.get("email", "")
        ip = request.headers.get("X-Forwarded-For", request.remote_addr)

        try:
            resp = requests.get(
                f"{IPASIS_API}/scan",
                params={"ip": ip, "email": email},
                headers={"Authorization": f"Bearer {IPASIS_KEY}"},
                timeout=3
            )
            data = resp.json()

            if data.get("email", {}).get("disposable"):
                return jsonify({"error": "Please use a permanent email"}), 400

        except requests.RequestException:
            pass  # Fail open

        return f(*args, **kwargs)
    return decorated
Enter fullscreen mode Exit fullscreen mode

What Signals Matter Most?

After analyzing thousands of signups, here's what actually predicts abuse:

Signal Weight Why
Disposable email 🔴 High Direct indicator of throwaway intent
Email domain age < 7 days 🟡 Medium Freshly registered domains are risky
VPN + disposable combo 🔴 Critical Privacy layering = almost always abuse
Tor exit node 🟡 Medium Legitimate privacy users exist, but flag for review
IP threat score > 75 🟡 Medium Correlates with bot farms and proxy networks

The magic is in combining signals. A VPN user with a Gmail address? Probably fine. A VPN user with a disposable email and a threat score of 90? That's abuse.

Try It Yourself

You can test any email or IP right now at ipasis.com/scan — no account needed for the web scanner. For API access, the free tier gives you 1,000 requests/day, which is plenty for most early-stage SaaS products.

Key Takeaways

  1. Don't just block — score. Binary blocklists create false positives. Score-based systems let you route suspicious signups to review queues instead of hard-blocking.

  2. Combine IP + email signals. Either alone gives you half the picture. Together, they catch coordinated abuse.

  3. Fail open. If your validation API is down, let signups through. A few bad actors are better than zero new users.

  4. Log everything. Even if you don't block, log the signals. You'll want this data when you analyze churn.

Stop letting disposable emails pollute your metrics. A single API call at signup can save you hours of cleanup later.

Building something and fighting signup abuse? I'd love to hear what's working for you — drop a comment below.

Top comments (0)