Mailbeam
Firebase + Cloud FunctionsIntermediate25 minutesUpdated January 2025

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 axios

Set 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 functions

Testing

// 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 (not onCreate) — beforeCreate runs before the record is saved, onCreate runs after
  • Always re-throw functions.auth.HttpsError and fail open for all other errors
  • Use functions.logger.error not console.error for structured logging in Firebase

Next steps