Block disposable email signups in Fastify
Use a preHandler hook on your signup route. Fastify's built-in schema validation makes the email-shape check explicit; the disposable check goes right after.
The code
// src/routes/signup.ts
import { FastifyPluginAsync } from 'fastify';
async function isDisposable(email: string) {
const r = await fetch(
`https://api.checkdisposable.email/v1/check?email=${encodeURIComponent(email)}`,
{ headers: { Authorization: `Bearer ${process.env.CDE_KEY!}` } }
);
if (!r.ok) return false;
return (await r.json()).is_disposable === true;
}
const signup: FastifyPluginAsync = async (app) => {
app.post(
'/signup',
{
schema: {
body: {
type: 'object',
required: ['email', 'password'],
properties: {
email: { type: 'string', format: 'email' },
password: { type: 'string', minLength: 8 },
},
},
},
preHandler: async (req, reply) => {
const { email } = req.body as { email: string };
if (await isDisposable(email)) {
reply.code(400).send({ error: 'Please use a real email address.' });
}
},
},
async (req) => {
// ...create user
return { ok: true };
}
);
};
export default signup;Notes
- preHandler vs handler
- Putting the check in `preHandler` means the main handler only runs for clean emails. Fastify automatically short-circuits the chain when the preHandler sends a response.
- Schema first
- The JSON schema rejects malformed emails before the disposable check, saving API calls for never-valid input.
- Plugin pattern
- Wrap the disposable check in a Fastify plugin (`fastify-plugin`) so any route can opt in by calling `app.register(disposablePlugin)` and adding `preHandler: app.blockDisposable`.
Get a free API key
500 checks/month, no credit card. No credit card. 30 seconds.
Sign up free →