Better Auth · TypeScript

Block disposable email in Better Auth

Better Auth fires a databaseHooks.user.create.before hook for every signup path — email/password, Google OAuth, magic link. One check covers them all.

The code

// lib/auth.ts
import { betterAuth } from "better-auth";

const PIISEND_URL = "https://api.piisend.com/api/v1/intelligence/email";

async function isDisposable(email: string) {
  const r = await fetch(PIISEND_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PIISEND_API_KEY!}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email }),
  });
  if (!r.ok) return false;
  return (await r.json()).signals?.disposable === true;
}

export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        before: async (user) => {
          if (user.email && await isDisposable(user.email)) {
            throw new Error("Please use a permanent email address.");
          }
          return { data: user };
        },
      },
    },
  },
});

API key

Keep `PIISEND_API_KEY` in server env only — Better Auth hooks run server-side.

Fail-open vs fail-closed

Wrap `isDisposable` in try/catch and return false on error if you prefer availability over strict blocking.

Pair with verification

The thrown error surfaces to your signup UI via Better Auth's normal error handling.

Send verification email with Piisend

After blocking disposable addresses, send OTP and verification mail through the same Piisend account. One API key for intelligence checks and transactional sends.