Integration guide · Clerk
How to block disposable emails in Clerk
Clerk already ships a disposable-email protection on every plan. Turn it on first. Then, if you run a custom sign-up flow, add one server check for the cases the toggle was not built to catch.
By the Vetto team · Updated
Quick answer
In the Clerk Dashboard, open Restrictions and turn on Block disposable email addresses. It is included on every plan and off by default. If you also want domains that cannot receive mail rejected (including typo domains like gmal.com), or your own allow and block rules, check the address in a Server Action before signUp.create() in a custom sign-up flow.
1. Turn on Clerk’s built-in protection
This is the right first step for every Clerk app, and it costs nothing. According to Clerk’s docs, the protection:
- blocks addresses whose domain is known to be disposable, on sign-up and when a user adds an email to an existing account;
- does not affect sign-ins, so existing users keep access;
- checks the domain and each parent domain, so
mail.example.comis caught ifexample.comis listed; - has no settings. If a domain your users rely on is blocked by mistake, you contact Clerk support.
While you are there, turn on Block email subaddresses too. It stops variations of an address that already has an account, which is the cheapest way one person farms several free trials.
2. What the toggle does not cover
| Case | Clerk toggle | Server check below |
|---|---|---|
| Known throwaway domain (mailinator.com) | Blocked | Blocked |
| Typo domain with no mail server (gmal.com) | Not its job | Rejected, with a “check for a typo” message |
| Domain that cannot receive mail | Not its job | Rejected |
| Your own allow or block rules | Support ticket / paid blocklist | Your code |
| Domains Clerk rates as risky | Business plan | Not covered |
If none of the middle rows matter to you, stop here: the toggle is enough. The rest of this page is for apps where a typo or a dead domain means a user who never gets the verification code and never comes back.
3. Add a server check to a custom flow
The check has to run between the user pressing submit and signUp.create(), so it needs a custom sign-up form rather than the prebuilt <SignUp /> component. It calls Vetto’s keyless domain lookup, which needs no account, and it fails open: if the lookup is slow or down, the signup goes ahead.
// lib/vetto.ts (server only, keyless, fails open)
import 'server-only';
const DEAD = new Set(['nxdomain', 'no_mx_a', 'null_mx', 'no_mail']);
export async function emailProblem(email: string): Promise<string | null> {
const domain = email.split('@').pop()?.trim().toLowerCase();
if (!domain) return null;
try {
const res = await fetch('https://usevetto.com/v1/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ domains: [domain] }),
signal: AbortSignal.timeout(2500),
cache: 'no-store',
});
const r = (await res.json()).results?.[domain];
if (r?.disposable) return 'Please use a permanent email address.';
if (r && DEAD.has(r.status)) return 'This email domain cannot receive mail. Check for a typo.';
} catch {
// timeout or network error: let the signup through
}
return null;
}// app/sign-up/actions.ts
'use server';
import { emailProblem } from '@/lib/vetto';
export async function checkSignupEmail(email: string) {
return emailProblem(email); // null means "go ahead"
}// app/sign-up/form.tsx (custom flow: you own the form)
'use client';
import { useSignUp } from '@clerk/nextjs';
import { checkSignupEmail } from './actions';
export function SignUpForm() {
const { isLoaded, signUp } = useSignUp();
// ...email/password state, error state
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (!isLoaded) return;
const problem = await checkSignupEmail(email);
if (problem) return setError(problem); // show it next to the field
await signUp.create({ emailAddress: email, password });
// ...continue with Clerk's email verification step
}
// ...
}Privacy relays: do not over-block
Apple Hide My Email, Firefox Relay and DuckDuckGo addresses forward to an inbox the person really reads. Many careful, paying users sign up with them. If you add your own block rules, leave relay domains out. Is privaterelay.appleid.com disposable? explains why.
Related: Better Auth · Auth.js / NextAuth · Next.js · Supabase · free trial abuse.
Frequently asked questions
Does Clerk block disposable emails?
Yes. Clerk has a "Block disposable email addresses" protection, included on every plan and off by default. Turn it on in the Clerk Dashboard. It blocks known disposable domains on sign-up and when a user adds an email, but not on sign-in.
Can I add my own domains to Clerk’s disposable list?
Not to that protection: Clerk documents it as having no settings, and asks you to contact support if a legitimate domain is blocked by mistake. Clerk’s general blocklist is a separate, paid feature. A server-side check like the one on this page gives you your own logic without a plan change.
What does Clerk’s protection not catch?
It only blocks domains already known to be disposable. It does not reject a typo domain such as gmal.com, or any other domain that simply has no mail server. Blocking domains Clerk rates as risky is a separate protection on the Business plan.
Does this work with Clerk’s prebuilt <SignUp /> component?
Turn on Clerk’s toggle for the prebuilt component. The extra server check on this page needs a custom sign-up flow, because you have to run code between the user pressing submit and signUp.create().
What about one person opening many free trials?
Turn on Clerk’s "Block email subaddresses" protection, which blocks variations of an address that already has an account. A disposable-domain list cannot see that pattern on its own.
Give your signup form sharp eyes.
Free on one site. One line of HTML. Remove it any time and your form works exactly as before.
Founding offer: Pro for life for $69 once, first 100 buyers, until Nov 27.