framework

Require work emails in Express

An Express middleware that checks signup emails with isBusinessEmail, blocks personal and disposable addresses, and fails open on timeouts.

isBusinessEmail for Express

A small middleware: check the email on the signup route, reject block, attach the verdict for allow and review, and never let our API become a single point of failure.

The middleware

// middleware/require-work-email.js
const API = 'https://api.isbusinessemail.com/v1/check';

export function requireWorkEmail({ policy = 'b2b', timeoutMs = 2500, field = 'email' } = {}) {
  return async function (req, res, next) {
    const email = String(req.body?.[field] ?? '').trim();
    if (!email) return res.status(422).json({ field, message: 'Email is required.' });

    let verdict;
    try {
      const r = await fetch(API, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.IBE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ email, policy }),
        signal: AbortSignal.timeout(timeoutMs),
      });
      if (r.ok) {
        verdict = await r.json();
      } else {
        req.log?.warn?.({ status: r.status }, 'isBusinessEmail error');
      }
    } catch (err) {
      req.log?.warn?.({ err: err.name }, 'isBusinessEmail unreachable');
    }

    // Fail open: no verdict means allow, flagged for a later re-check.
    req.emailVerdict = verdict ?? { recommendation: 'allow', failOpen: true };

    if (req.emailVerdict.recommendation === 'block') {
      return res.status(422).json({
        field,
        code: 'work_email_required',
        message: 'Please use your work email.',
        did_you_mean: req.emailVerdict.did_you_mean ?? null,
      });
    }
    next();
  };
}

Use it

import express from 'express';
import { requireWorkEmail } from './middleware/require-work-email.js';

const app = express();
app.use(express.json());

app.post('/signup', requireWorkEmail(), async (req, res) => {
  const v = req.emailVerdict;
  const user = await createUser({
    email: req.body.email,
    emailCategory: v.category ?? null,
    needsReview: v.recommendation === 'review' || v.failOpen === true,
    microsoft365: v.workspace?.microsoft_365?.detected ?? null,
    googleWorkspace: v.workspace?.google_workspace?.detected ?? null,
  });
  res.status(201).json({ id: user.id });
});

app.listen(3000);

Notes

  • Node 18+ provides fetch and AbortSignal.timeout; no dependencies needed.
  • 422 with did_you_mean lets your frontend show “Did you mean …?” next to the field.
  • Use requireWorkEmail({ policy: 'strict' }) on routes that need a company tenant, such as starting a Microsoft 365 integration.
  • Unit-test with test addresses, and mock fetch for the timeout path.

Official docs: Express. See also: Signup form guide · Code examples.