Block disposable signups in Auth0 with a Pre User Registration Action

Auth0's built-in disposable-domain check is a static list. Add a Mailsurity call to a Pre User Registration Action and deny the signup before the user is even created.

Why Auth0's built-in check isn't enough

Auth0 can flag well-known disposable domains, but that check runs against a static list. It doesn't catch a throwaway domain registered an hour ago, or the long tail of rotating lookalike domains that all point at the same handful of temp-mail servers. Those pass through untouched.

Add a Pre User Registration Action

Auth0 Actions let you run custom code during the signup flow. The Pre User Registration trigger runs before the user is created, so calling api.access.deny() stops a disposable signup outright, leaving no account to clean up afterward, unlike a post-creation ban.

In the Auth0 dashboard: Actions → Flows → Pre User Registration, create a new custom action, add mailsurity as a dependency, and add MAILSURITY_API_KEY as a secret (Action settings, not an environment variable, since Actions run in their own sandbox).

Auth0 Action: Pre User Registration
// Add "mailsurity" in the Action's dependency panel (left sidebar).
const { Mailsurity } = require('mailsurity');

exports.onExecutePreUserRegistration = async (event, api) => {
  const ms = new Mailsurity({ apiKey: event.secrets.MAILSURITY_API_KEY });

  if (await ms.isDisposable(event.user.email)) {
    // Blocks the registration outright: the account is never created.
    api.access.deny(
      'disposable_email',
      'Please sign up with a permanent email address.',
    );
  }
};

A note on fail-open behavior

The Mailsurity SDK fails open by default: a timeout or transient error resolves isDisposable() to false rather than throwing, so a Mailsurity outage never blocks a real signup. The default 2-second timeout comfortably fits inside Auth0 Actions' execution window given Mailsurity's <100ms p99 latency.

Why this catches more than the static list

Mailsurity fingerprints the mail servers behind throwaway addresses, so a brand-new disposable domain is flagged the moment it points at a known throwaway host, before any list has heard of it. Run the audit on your real Auth0 signups to see how many are getting through today.

A more precise alternative: branch on a decision, not just a boolean

isDisposable() above gives a yes/no answer. Mailsurity's decision API, POST /api/assess, returns a risk score plus a resolved ALLOW / VERIFY / BLOCK decision instead, so a signup that's genuinely on the fence (a new corporate domain, a borderline heuristic score) doesn't have to land in the same bucket as an obvious disposable address. It gives you a third option between “let them in” and “reject outright”: allow on ALLOW, deny on BLOCK, and flag VERIFY for extra scrutiny (a manual review queue, step-up MFA, or an email-confirmation gate) instead of forcing a binary call on a case the system itself isn't sure about.

Auth0 Action: Pre User Registration (decision API)
// The mailsurity SDK doesn't wrap /api/assess yet, so this Action calls it
// directly with fetch. Add MAILSURITY_API_KEY as an Action secret, same as
// the isDisposable() example above.
exports.onExecutePreUserRegistration = async (event, api) => {
  let decision = 'ALLOW'; // fail open: an outage should never block a real signup
  try {
    const res = await fetch('https://mailsurity.com/api/assess', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${event.secrets.MAILSURITY_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email: event.user.email }),
    });
    if (res.ok) ({ decision } = await res.json());
  } catch {
    // network error or timeout — decision stays 'ALLOW'
  }

  if (decision === 'BLOCK') {
    // High-confidence bad signal (e.g. a known disposable domain, or a
    // domain registered minutes ago): deny outright, same as the boolean
    // example, but now driven by a risk score instead of a plain flag.
    api.access.deny(
      'fake_signup',
      'Please sign up with a permanent email address.',
    );
    return;
  }

  if (decision === 'VERIFY') {
    // Genuinely ambiguous: let the account through, but flag it so a later
    // step (email confirmation gate, manual review queue, step-up MFA) can
    // treat it with more scrutiny than a clean ALLOW.
    api.user.setAppMetadata('signupRisk', 'verify');
  }
  // decision === 'ALLOW' falls through with no changes.
};

This doesn't replace the isDisposable() approach above. Both call the same underlying detection, and the boolean endpoint isn't going anywhere; use whichever shape matches how precisely you want to react to a borderline signup.

See how many disposable signups slip past your current setup

Paste a list of your signups. Mailsurity shows how many a standard blocklist misses that we catch by mail-server fingerprint. No signup needed; the part before the @ never leaves your browser.

Need the full field and error reference? See the API reference.