Email Verification with Firebase
Firebase Authentication allows you to intercept sign-up events via beforeCreate blocking functions. This tutorial adds Mailbeam verification to that hook so invalid and disposable emails are blocked before a Firebase user record is created.
Prerequisites
- Firebase project with Blaze plan (required for Cloud Functions)
- Firebase CLI installed
- A Mailbeam API key (sign up free)
Setup
firebase init functions
cd functions
npm install axiosSet the Mailbeam key as a Firebase environment variable:
firebase functions:config:set mailbeam.key="mb_live_xxxxxxxxxxxxxxxxxxxx"Create the blocking function
// functions/index.js
const functions = require("firebase-functions");
const { getAuth } = require("firebase-admin/auth");
const axios = require("axios");
// beforeCreate runs BEFORE the user record is written to Firebase Auth
exports.validateEmailBeforeCreate = functions.auth
.user()
.beforeCreate(async (user, context) => {
const email = user.email;
if (!email) return; // No email — other providers, skip
const apiKey = functions.config().mailbeam.key;
try {
const { data } = await axios.post(
"https://api.mailbeam.dev/v1/verify",
{ email },
{
headers: { Authorization: `Bearer ${apiKey}` },
timeout: 5000,
}
);
const { valid, score, reason } = data;
if (!valid || score < 60) {
const message =
reason === "disposable_domain"
? "Please use a permanent email address, not a temporary one."
: "Please provide a valid, reachable email address.";
throw new functions.auth.HttpsError("invalid-argument", message);
}
} catch (err) {
// If it's already our HttpsError, re-throw it
if (err.code === "functions/invalid-argument") throw err;
// Otherwise: log and fail open
functions.logger.error("Mailbeam verification failed", { error: err.message });
}
});TypeScript version
// functions/src/index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import axios from "axios";
admin.initializeApp();
export const validateEmailBeforeCreate = functions.auth
.user()
.beforeCreate(async (user) => {
if (!user.email) return;
const apiKey = functions.config().mailbeam.key;
try {
const { data } = await axios.post<{
valid: boolean;
score: number;
reason: string | null;
}>(
"https://api.mailbeam.dev/v1/verify",
{ email: user.email },
{ headers: { Authorization: `Bearer ${apiKey}` }, timeout: 5000 }
);
if (!data.valid || data.score < 60) {
throw new functions.auth.HttpsError(
"invalid-argument",
data.reason === "disposable_domain"
? "Please use a permanent email address."
: "Please use a valid, reachable email address."
);
}
} catch (err: unknown) {
if ((err as { code?: string }).code === "functions/invalid-argument") throw err;
functions.logger.error("Mailbeam error", err);
}
});Deploy
firebase deploy --only functionsTesting
// Use Firebase Emulator Suite to test locally
firebase emulators:start
// Try creating a user with a disposable email via Firebase SDK
const { createUserWithEmailAndPassword } = require("firebase/auth");
try {
await createUserWithEmailAndPassword(auth, "temp@mailinator.com", "password");
} catch (error) {
console.log(error.code); // "auth/invalid-argument"
console.log(error.message); // "Please use a permanent email address."
}Best practices
- Use
beforeCreate(notonCreate) —beforeCreateruns before the record is saved,onCreateruns after - Always re-throw
functions.auth.HttpsErrorand fail open for all other errors - Use
functions.logger.errornotconsole.errorfor structured logging in Firebase