Supabase Auth creates users from your client with supabase.auth.signUp. To check the address first, put a small server-side step in front of it: a Supabase Edge Function that calls isBusinessEmail with your secret key and only then signs the user up.
1. Add the secret
supabase secrets set IBE_API_KEY=ibe_live_…
2. The Edge Function
// supabase/functions/signup/index.ts
import { createClient } from 'npm:@supabase/supabase-js@2';
async function checkWorkEmail(email: string) {
try {
const res = await fetch('https://api.isbusinessemail.com/v1/check', {
method: 'POST',
headers: {
Authorization: `Bearer ${Deno.env.get('IBE_API_KEY')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(2500),
});
return res.ok ? await res.json() : { recommendation: 'allow', failOpen: true };
} catch {
return { recommendation: 'allow', failOpen: true };
}
}
Deno.serve(async (req) => {
const { email, password } = await req.json();
const verdict = await checkWorkEmail(email);
if (verdict.recommendation === 'block') {
return Response.json(
{ error: 'work_email_required', did_you_mean: verdict.did_you_mean ?? null },
{ status: 422 },
);
}
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_ANON_KEY')!);
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
email_category: verdict.category ?? null,
microsoft_365: verdict.workspace?.microsoft_365?.detected ?? null,
google_workspace: verdict.workspace?.google_workspace?.detected ?? null,
},
},
});
if (error) return Response.json({ error: error.message }, { status: 400 });
return Response.json({ user: data.user?.id ?? null });
});
Call this function from your sign-up form instead of calling signUp directly. Add CORS headers if your frontend is on another origin. The verdict ends up in the user’s metadata, where your app can read it.
3. Make it hard to bypass
A client holding your project’s public key can still call signUp directly and skip the function. For a stronger guarantee, pick one:
- Auth Hooks. Supabase can call your HTTP endpoint or a Postgres function during auth events. Check the Supabase Auth Hooks documentation for a hook that runs before a user is created, and call isBusinessEmail from there.
- Server-created users. Turn off public sign-ups and create users from the function with the service-role key through the Auth admin API.
- Gate features, not signups. Let anyone sign up, but check
email_categorybefore enabling team features or trials (enforced with Row Level Security or in your API).
Tips
- Fail open on timeouts; mark those users for a background re-check.
- Magic-link and OAuth sign-ins also create users. If you use them, classify after creation (for example from a database trigger that enqueues a job) as a backstop.
- Test addresses like
disposable@test.isbusinessemail.comwork without a key (list).
Official docs: Supabase Auth. See also: Signup form guide.