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).
// 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.
// 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.