Error Handling

Your signup flow should never break because of an email check. Here's how to handle errors gracefully.

Key principle

If the API is down, fall back to offline detection. If offline detection fails, let the user through. Never block a real user because of a service issue.

The fallback pattern

Use the API for full checks, fall back to the offline blocklist if the API is unreachable:

lib/email-check.tstypescript
import { isDisposable, createIsDisposable } from '@isdisposable/js';

const checker = createIsDisposable({
  apiKey: process.env.ISDISPOSABLE_API_KEY!,
  timeout: 5000, // 5 second timeout
});

async function checkEmail(email: string) {
  try {
    // Try API first (real-time DNS + scoring)
    const result = await checker.check(email);
    return {
      disposable: result.disposable,
      score: result.score,
      source: 'api',
    };
  } catch (error) {
    // API failed — fall back to offline check
    console.warn('[isDisposable] API unavailable, using offline check');
    return {
      disposable: isDisposable(email),
      score: isDisposable(email) ? 95 : 0,
      source: 'offline_fallback',
    };
  }
}

API error types

429Rate limit exceeded

You're sending too many requests. Wait and retry, or fall back to offline mode.

401Invalid API key

Your API key is wrong or revoked. Check the dashboard.

400Invalid email

The email format is invalid. Validate the format on your end before calling the API.

500Server error

Something went wrong on our side. Fall back to offline mode and retry later.

Retry with backoff

For production apps, implement exponential backoff for transient errors:

lib/email-check.tstypescript
async function checkWithRetry(email: string, retries = 2) {
  for (let i = 0; i <= retries; i++) {
    try {
      return await checker.check(email);
    } catch (error: any) {
      // Don't retry client errors (400, 401)
      if (error?.status === 400 || error?.status === 401) throw error;

      if (i === retries) {
        // All retries failed — use offline fallback
        return {
          disposable: isDisposable(email),
          score: isDisposable(email) ? 95 : 0,
          reason: 'offline_fallback',
          cached: false,
        };
      }

      // Wait before retrying: 200ms, 400ms
      await new Promise(r => setTimeout(r, 200 * Math.pow(2, i)));
    }
  }
}

Timeout configuration

Set a timeout to prevent slow DNS lookups from blocking your signup flow:

lib/checker.tstypescript
const checker = createIsDisposable({
  apiKey: process.env.ISDISPOSABLE_API_KEY!,
  timeout: 3000, // Fail fast after 3 seconds
});

DNS checks take time

The API performs real-time DNS and RDAP lookups which can take 100-500ms. The blocklist check alone is instant (<1ms). If speed is critical, use the offline package and only call the API for emails that pass the local check.

Smart hybrid approach

The most efficient pattern: check offline first, only call the API if the email looks clean:

lib/email-check.tstypescript
import { isDisposable, createIsDisposable } from '@isdisposable/js';

const checker = createIsDisposable({
  apiKey: process.env.ISDISPOSABLE_API_KEY!,
});

async function smartCheck(email: string) {
  // Step 1: Instant offline check (free, <1ms)
  if (isDisposable(email)) {
    return { disposable: true, score: 95, reason: 'blocklist_match' };
  }

  // Step 2: Only call API for emails that passed offline check
  // This saves API quota and is faster for known domains
  try {
    return await checker.check(email);
  } catch {
    return { disposable: false, score: 0, reason: 'offline_clean' };
  }
}

Save API calls

This pattern saves ~80% of your API quota because most disposable emails are caught by the offline check. You only burn an API call for emails that need deeper DNS analysis.