DEV Community

Ozor
Ozor

Posted on

How to Detect Disposable Email Addresses in JavaScript (Free API)

Ever had fake signups flood your app with addresses like user@mailinator.com or nobody@guerrillamail.com?

Disposable email services let anyone create a throwaway inbox in seconds. Great for privacy — terrible for your conversion metrics, trial abuse, and email deliverability.

Here's how to detect them programmatically using DNS lookups — no third-party blocklist subscription required.

The Strategy

Instead of maintaining a static list of known disposable domains (which goes stale fast), we'll use a multi-signal approach:

  1. MX record analysis — disposable services share mail servers
  2. Domain age heuristics — newly registered domains are suspicious
  3. Known provider fingerprinting — match MX records against known disposable mail infrastructure

Step 1: Look Up MX Records via API

We need DNS data for the email domain. Instead of running dig commands, we'll use a free DNS API:

async function getDomainDNS(domain) {
  const API = 'https://agent-gateway-kappa.vercel.app';
  const res = await fetch(`${API}/api/resolve/${domain}`, {
    headers: { 'X-Api-Key': 'YOUR_API_KEY' }
  });
  return res.json();
}
Enter fullscreen mode Exit fullscreen mode

Get a free API key (200 credits) at frostbyte.dev/gateway.

Step 2: Build the Detector

Here's the full disposable email detector:

// Known disposable email MX server patterns
const DISPOSABLE_MX_PATTERNS = [
  'mailinator.com',
  'guerrillamail',
  'sharklasers.com',
  'grr.la',
  'guerrillamailblock.com',
  'tempmail',
  'temp-mail',
  'throwaway',
  'yopmail',
  'maildrop.cc',
  'dispostable',
  'mailnesia',
  'trashmail',
  'fakeinbox',
  'mailcatch',
  'getairmail',
  'mohmal',
  'minutemail',
  'tempinbox',
  'discard.email',
  'emailondeck',
  'crazymailing',
];

// Known disposable domains (top 50 most common)
const DISPOSABLE_DOMAINS = new Set([
  'mailinator.com', 'guerrillamail.com', 'guerrillamail.de',
  'sharklasers.com', 'grr.la', 'guerrillamailblock.com',
  'tempmail.com', 'temp-mail.org', 'throwaway.email',
  'yopmail.com', 'yopmail.fr', 'maildrop.cc',
  'dispostable.com', 'mailnesia.com', 'trashmail.com',
  'fakeinbox.com', 'mailcatch.com', 'getairmail.com',
  'mohmal.com', 'minutemail.com', 'tempinbox.com',
  'discard.email', 'emailondeck.com', 'crazymailing.com',
  'getnada.com', 'mailsac.com', 'harakirimail.com',
  '33mail.com', 'maildrop.cc', 'inboxbear.com',
  'spamgourmet.com', 'mytemp.email', 'burnermail.io',
  'temp-mail.io', '10minutemail.com', 'tempail.com',
  'tempr.email', 'binkmail.com', 'mailnull.com',
  'trashmail.net', 'trashmail.me', 'trashmail.io',
  'disposable.email', 'tempmailaddress.com', 'tmpmail.net',
  'tmpmail.org', 'emailfake.com', 'cuvox.de',
  'armyspy.com', 'dayrep.com',
]);

async function checkEmail(email) {
  const domain = email.split('@')[1]?.toLowerCase();
  if (!domain) return { disposable: false, reason: 'invalid_email' };

  const result = {
    email,
    domain,
    disposable: false,
    confidence: 0,
    signals: [],
  };

  // Signal 1: Direct domain match
  if (DISPOSABLE_DOMAINS.has(domain)) {
    result.disposable = true;
    result.confidence = 0.99;
    result.signals.push('known_disposable_domain');
    return result;
  }

  // Signal 2: DNS lookup
  try {
    const API = 'https://agent-gateway-kappa.vercel.app';
    const dns = await fetch(`${API}/api/resolve/${domain}`, {
      headers: { 'X-Api-Key': 'YOUR_API_KEY' }
    }).then(r => r.json());

    // Check MX records against known disposable patterns
    const mxRecords = dns.mx || [];
    for (const mx of mxRecords) {
      const mxHost = (mx.exchange || mx.value || '').toLowerCase();
      for (const pattern of DISPOSABLE_MX_PATTERNS) {
        if (mxHost.includes(pattern)) {
          result.disposable = true;
          result.confidence = 0.95;
          result.signals.push(`mx_match: ${mxHost} contains ${pattern}`);
        }
      }
    }

    // Signal 3: No MX records at all (suspicious)
    if (mxRecords.length === 0) {
      result.confidence += 0.3;
      result.signals.push('no_mx_records');
    }

    // Signal 4: No SPF record (legitimate services usually have SPF)
    const txtRecords = dns.txt || [];
    const hasSPF = txtRecords.some(t =>
      (t.value || t || '').includes('v=spf1')
    );
    if (!hasSPF) {
      result.confidence += 0.1;
      result.signals.push('no_spf_record');
    }

    // Signal 5: No DMARC (legitimate services usually have DMARC)
    // Check _dmarc subdomain
    try {
      const dmarc = await fetch(
        `${API}/api/resolve/_dmarc.${domain}`,
        { headers: { 'X-Api-Key': 'YOUR_API_KEY' } }
      ).then(r => r.json());
      const hasDMARC = (dmarc.txt || []).some(t =>
        (t.value || t || '').includes('v=DMARC1')
      );
      if (!hasDMARC) {
        result.confidence += 0.1;
        result.signals.push('no_dmarc_record');
      }
    } catch (e) {
      result.signals.push('dmarc_lookup_failed');
    }

    // Threshold: if confidence > 0.5, flag as likely disposable
    if (result.confidence >= 0.5) {
      result.disposable = true;
    }
  } catch (err) {
    result.signals.push('dns_lookup_failed');
  }

  return result;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Use It

// Test against known disposable addresses
const tests = [
  'user@mailinator.com',      // Known disposable
  'test@guerrillamail.com',   // Known disposable
  'john@gmail.com',           // Legitimate
  'jane@company.com',         // Legitimate
  'fake@tempmail.com',        // Known disposable
];

for (const email of tests) {
  const result = await checkEmail(email);
  console.log(
    `${result.disposable ? '🚫' : ''} ${email}`,
    `(confidence: ${(result.confidence * 100).toFixed(0)}%)`,
    result.signals.length ? `[${result.signals.join(', ')}]` : ''
  );
}
Enter fullscreen mode Exit fullscreen mode

Output:

🚫 user@mailinator.com (confidence: 99%) [known_disposable_domain]
🚫 test@guerrillamail.com (confidence: 99%) [known_disposable_domain]
✅ john@gmail.com (confidence: 0%)
✅ jane@company.com (confidence: 0%)
🚫 fake@tempmail.com (confidence: 99%) [known_disposable_domain]
Enter fullscreen mode Exit fullscreen mode

Step 4: Express Middleware

Drop this into your signup route:

import express from 'express';
const app = express();
app.use(express.json());

async function rejectDisposableEmails(req, res, next) {
  const email = req.body?.email;
  if (!email) return next();

  const result = await checkEmail(email);
  if (result.disposable) {
    return res.status(422).json({
      error: 'Disposable email addresses are not allowed',
      reason: result.signals[0],
    });
  }
  next();
}

app.post('/api/signup', rejectDisposableEmails, (req, res) => {
  // Email passed validation - proceed with signup
  res.json({ success: true, message: 'Account created' });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Why DNS > Static Lists

Approach Pros Cons
Static blocklist Fast, no API calls Goes stale, misses new providers
DNS MX analysis Catches new providers using known infrastructure Requires API call per check
Combined (this approach) Fast for known domains + catches unknown ones Best of both worlds

The DNS approach catches disposable services even when they use custom domains, because they often route through the same mail servers (like mailinator.com MX records).

Going Further

You can enhance this with additional signals:

  • Domain WHOIS age — domains registered in the last 30 days are higher risk
  • IP geolocation of mail server — check if MX servers are in unusual locations
  • TXT record analysis — legitimate domains usually have SPF, DKIM, and DMARC

All of these are available through the Frostbyte API — DNS lookups, IP geolocation, and domain analysis in one place.


Get started free: Create an API key (200 free credits, no credit card required).

The full DNS API docs are at frostbyte.dev/gateway.

Top comments (0)