Skip to content

Sniffmail/sniffmail

Repository files navigation

sniffmail

A comprehensive email validation library that detects disposable/burner email addresses and verifies mailbox existence via SMTP.

Features

  • Syntax Validation — Validates email format
  • Disposable Email Detection — Blocks burner/temporary email addresses using multiple sources:
    • GitHub disposable email blocklist (26,000+ domains)
    • Scraped domains from temp mail providers
    • Live-discovered domains from temp mail APIs
    • DeBounce API real-time detection
  • MX Record Lookup — Verifies the domain has mail servers
  • Deep SMTP Verification — Verifies the mailbox actually exists (powered by Sniffmail API)
  • Caching — In-memory or Redis caching to avoid redundant checks
  • Batch Processing — Validate multiple emails with concurrency control

Installation

npm install sniffmail

Quick Start

import { validateEmail } from 'sniffmail';

// Basic validation (syntax + disposable + MX check) — FREE, no API key needed
const result = await validateEmail('[email protected]');

if (!result.valid) {
  console.log(`Invalid: ${result.reason}`);
  // Possible reasons: 'invalid_syntax', 'disposable', 'no_mx_records'
}

Deep Mode (SMTP Verification)

Verify that a mailbox actually exists — catches fake emails like [email protected].

import { configure, validateEmail } from 'sniffmail';

// Get your free API key at https://sniffmail.io
configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
});

const result = await validateEmail('[email protected]', { deep: true });

if (result.smtp?.is_reachable === 'invalid') {
  console.log('Mailbox does not exist!');
}

Pricing

Up to 85% cheaper than ZeroBounce, NeverBounce, and Hunter.

Tier Verifications Rate Limit Price
Free 500/month 10 req/min $0
Starter 25,000/month 60 req/min $29/mo
Pro 100,000/month 200 req/min $79/mo
Scale 200,000/month 200 req/min $149/mo

Also available: Credit Packs for pay-as-you-go usage (as low as $0.001/email).

Get your API key at https://sniffmail.io

API Reference

validateEmail(email, options?)

Validates a single email address.

const result = await validateEmail('[email protected]', {
  deep: false,        // Enable SMTP verification (default: false)
  checkMx: true,      // Check MX records (default: true)
  useDeBounce: true,  // Use DeBounce API (default: true)
  timeout: 5,         // DNS timeout in seconds (default: 5)
});

Returns: ValidationResult

{
  email: string;           // Normalized email address
  valid: boolean;          // Overall validity
  reason: ValidationReason | null;  // Why it's invalid
  disposable: boolean;     // Is it a disposable/burner email
  mx: boolean;             // Has valid MX records
  smtp: SmtpResult | null; // SMTP details (only with deep: true)
  cached: boolean;         // Was result from cache
}

Validation Reasons:

  • invalid_syntax — Email format is invalid
  • disposable — Domain is a known disposable email provider
  • no_mx_records — Domain has no mail servers
  • mailbox_not_found — Mailbox doesn't exist (deep mode)
  • mailbox_full — Mailbox is full (deep mode)
  • mailbox_disabled — Mailbox is disabled (deep mode)
  • catch_all — Domain accepts all emails (deep mode)
  • smtp_error — Could not connect to mail server (deep mode)

validateEmails(emails, options?)

Validates multiple emails with concurrency control.

import { validateEmails } from 'sniffmail';

const { results, summary } = await validateEmails(
  ['[email protected]', '[email protected]', '[email protected]'],
  {
    deep: true,
    concurrency: 5,  // Max parallel requests (default: 5)
  }
);

console.log(summary);
// { total: 3, valid: 2, invalid: 0, disposable: 1, unknown: 0 }

configure(options)

Configure global settings.

import { configure } from 'sniffmail';

configure({
  apiKey: 'sniff_xxx',  // Your Sniffmail API key
  cache: {
    enabled: true,
    ttl: {
      safe: 604800,     // 7 days
      invalid: 2592000, // 30 days
      risky: 86400,     // 1 day
      unknown: 0,       // Don't cache
    },
  },
});

isDisposableDomain(domain)

Quick synchronous check if a domain is disposable (no network calls, no API key needed).

import { isDisposableDomain } from 'sniffmail';

if (isDisposableDomain('tempmail.com')) {
  console.log('Blocked!');
}

Environment Variables

# Your Sniffmail API key (required for deep mode)
SNIFFMAIL_API_KEY=sniff_xxxxx

SMTP Result Details

When using deep: true, the smtp field contains:

{
  is_reachable: 'safe' | 'risky' | 'invalid' | 'unknown';
  can_connect: boolean;    // Could connect to SMTP server
  is_deliverable: boolean; // Mailbox accepts mail
  is_catch_all: boolean;   // Domain accepts all addresses
}

Reachability statuses:

  • safe — Email exists and is deliverable
  • risky — Email exists but might bounce (catch-all, full mailbox)
  • invalid — Email does not exist
  • unknown — Could not determine (timeout, blocked, etc.)

Usage Examples

Signup Form Validation

import { validateEmail } from 'sniffmail';

async function validateSignupEmail(email: string): Promise<string | null> {
  const result = await validateEmail(email);

  if (!result.valid) {
    switch (result.reason) {
      case 'invalid_syntax':
        return 'Please enter a valid email address';
      case 'disposable':
        return 'Disposable email addresses are not allowed';
      case 'no_mx_records':
        return 'This email domain does not exist';
      default:
        return 'Please enter a valid email address';
    }
  }

  return null; // Valid
}

Deep Verification for Critical Flows

import { configure, validateEmail } from 'sniffmail';

// Configure once at app startup
configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
});

async function verifyEmailForInvoice(email: string): Promise<boolean> {
  const result = await validateEmail(email, { deep: true });

  if (!result.valid) {
    console.log(`Email ${email} failed: ${result.reason}`);
    return false;
  }

  if (result.smtp?.is_reachable === 'risky') {
    console.log(`Warning: ${email} is risky (catch-all or full inbox)`);
  }

  return true;
}

Bulk Email List Cleaning

import { configure, validateEmails } from 'sniffmail';

configure({ apiKey: process.env.SNIFFMAIL_API_KEY });

async function cleanEmailList(emails: string[]): Promise<string[]> {
  const { results } = await validateEmails(emails, {
    deep: true,
    concurrency: 10,
  });

  return results
    .filter(r => r.valid && r.smtp?.is_reachable === 'safe')
    .map(r => r.email);
}

Error Handling

import {
  validateEmail,
  ApiKeyNotConfiguredError,
  SniffmailError
} from 'sniffmail';

try {
  const result = await validateEmail('[email protected]', { deep: true });
} catch (error) {
  if (error instanceof ApiKeyNotConfiguredError) {
    console.error('API key not set. Get one at https://sniffmail.io');
  } else if (error instanceof SniffmailError) {
    if (error.statusCode === 403) {
      console.error('Usage limit exceeded. Upgrade at https://sniffmail.io');
    } else if (error.statusCode === 429) {
      console.error('Rate limited. Slow down requests.');
    }
  }
}

Caching

Results are cached to avoid redundant verification:

Status Default TTL Reason
safe 7 days Valid emails rarely change
invalid 30 days Invalid emails almost never become valid
risky 1 day Status may change
unknown 0 (no cache) Retry on next request

Redis Cache

import { configure, RedisCache } from 'sniffmail';
import Redis from 'ioredis';

const redis = new Redis('redis://localhost:6379');

configure({
  apiKey: process.env.SNIFFMAIL_API_KEY,
  cache: {
    store: new RedisCache(redis),
  },
});

Network Requirements

If you're running Sniffmail behind a firewall or in a restricted network environment, the SDK makes outbound requests to the following domains:

Domain Purpose Required
raw.githubusercontent.com Fetches community-maintained disposable email blocklist (26,000+ domains). Refreshed every 24 hours. Yes
disposable.debounce.io Real-time disposable email detection API (free). Results cached for 24 hours. No (disable with useDeBounce: false)
deviceandbrowserinfo.com Scrapes disposable email domains from temp mail providers. Refreshed every 24 hours. No (falls back to hardcoded list)
api.sniffmail.io Sniffmail API for deep SMTP verification and telemetry. Only for deep: true mode

If GitHub (raw.githubusercontent.com) is unreachable, the SDK falls back to a hardcoded list of ~100+ known disposable domains, so basic disposable detection still works offline.

License

MIT

Releases

No releases published

Packages

 
 
 

Contributors