Firebase Authentication will happily create an account for x7k2@mailinator.com, and for jane@gmial.com. The first abuses your free tier; the second is a real user who will never get your verification email. Either way, the email you send bounces, and bounce rates are how Gmail and Outlook decide whether to trust you.
Firebase has a built-in place to stop this: blocking functions. A beforeUserCreated function runs before the account is saved, and throwing an error cancels the signup and sends your message back to the client.
Blocking functions need Firebase Authentication with Identity Platform (a free upgrade in the Firebase console under Authentication → Settings) and the Blaze plan for Cloud Functions.
What to block
reason from the verification API |
Block? |
|---|---|
disposable, mailbox_not_found, invalid_syntax, spamtrap, mailbox_disabled |
✅ Block |
catch_all, unverifiable, inbox_full, role_account, mailbox_exists |
❌ Allow |
Catch-all and unverifiable results are common for real users at companies, so let them in. And if the verification call fails, allow the signup (fail open); an outage should never lock out real users.
The blocking function
// functions/src/index.ts
import { beforeUserCreated, HttpsError } from "firebase-functions/v2/identity";
import { defineSecret } from "firebase-functions/params";
const MAILRAMBO_KEY = defineSecret("MAILRAMBO_KEY");
const BLOCK = new Set([
"disposable", "mailbox_not_found", "invalid_syntax", "spamtrap", "mailbox_disabled",
]);
async function verify(email: string) {
try {
const res = await fetch("https://www.mailrambo.com/v1/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${MAILRAMBO_KEY.value()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(4000),
});
return res.ok ? ((await res.json()) as { deliverable: boolean; reason: string }) : null;
} catch {
return null; // fail open
}
}
export const screenSignups = beforeUserCreated({ secrets: [MAILRAMBO_KEY] }, async (event) => {
const email = event.data?.email;
if (!email) return; // phone auth etc.
const verdict = await verify(email);
if (!verdict || !BLOCK.has(verdict.reason)) return; // allow
throw new HttpsError(
"invalid-argument",
verdict.reason === "disposable"
? "Please use a permanent email address, not a temporary one."
: "That email address doesn't seem to exist. Check for typos?",
);
});
Deploy it:
firebase functions:secrets:set MAILRAMBO_KEY # paste mr_live_...
firebase deploy --only functions:screenSignups
Then register it under Authentication → Settings → Blocking functions → Before account creation.
Show the message in your app
Firebase wraps blocking-function errors as auth/internal-error, with your message embedded in the error text (… Message: "Please use a permanent email address…"). Pull it out, and keep a generic fallback because some SDK versions only return a bare error code:
try {
await createUserWithEmailAndPassword(auth, email, password);
} catch (e: any) {
const match = /Message: \\?"([^"\\]+)/.exec(e?.message ?? "");
setFormError(match ? match[1] : "Please sign up with a valid, permanent email address.");
}
Blocking functions must answer within 7 seconds, cold start included. The 4-second timeout on the verification call keeps you inside that limit; if cold starts are a problem, set minInstances: 1 on the function.
Test for free
Use a MailRambo test key (mr_test_…, created on the API keys page) in your staging project. It never contacts mail servers or uses credits:
deliverable@example.com→ account createddisposable@example.com→ blockednot_found@example.com→ blocked
Cost
One check per signup; invalid syntax is free. 100 free checks a month, then plans from $5 for 1,000, or pay-as-you-go packs that never expire.
Tip: add
?mode=fastfor a free check that answers in under a second (syntax, typos likegmial.com, domains with no mail server, disposable providers). Use it inline on the form and keep the full check for when you need a confirmed yes/no. See fast mode.
Try an address by hand with the free disposable email checker.