Block disposable email signups on Supabase
Use a Supabase Edge Function as an Auth Hook. Calls CheckDisposable Email before Supabase creates the user — rejects the signup at the auth layer so no row is ever written to `auth.users`.
The code
// supabase/functions/before-user-created/index.ts
// Deploy with: supabase functions deploy before-user-created
// Wire as: Database → Webhooks → "Send before user is created" → this function
Deno.serve(async (req) => {
const body = await req.json();
const email = body.user_metadata?.email ?? body.email;
const cdeKey = Deno.env.get('CDE_KEY');
if (!email || !cdeKey) {
return new Response(JSON.stringify({ decision: 'continue' }), {
headers: { 'content-type': 'application/json' },
});
}
try {
const r = await fetch(
`https://api.checkdisposable.email/v1/check?email=${encodeURIComponent(email)}`,
{ headers: { Authorization: `Bearer ${cdeKey}` } }
);
if (r.ok) {
const data = await r.json();
if (data.is_disposable) {
return new Response(
JSON.stringify({
decision: 'reject',
message: 'Please use a real email address.',
}),
{ headers: { 'content-type': 'application/json' } }
);
}
}
} catch {
// fail open: let Supabase continue with the signup
}
return new Response(JSON.stringify({ decision: 'continue' }), {
headers: { 'content-type': 'application/json' },
});
});Notes
- Set the API key as a secret
- Run `supabase secrets set CDE_KEY=cde_live_xxx` so the Edge Function has access to your key without committing it to git.
- Auth hook wiring
- In the Supabase dashboard: Authentication → Hooks → enable "Send before user is created (Custom Access Token / Pre-signup)" and point it at this Edge Function. The hook contract is: return `decision: "continue"` to proceed, `decision: "reject"` to block with a message.
- No row created on reject
- Because we hook BEFORE user creation, rejected signups never write a row to `auth.users`. No cleanup job needed.
Get a free API key
500 checks/month, no credit card. No credit card. 30 seconds.
Sign up free →