Block disposable email signups in Remix
Add the check inside your signup `action`. Remix's nested error UI surfaces it cleanly via `useActionData()` without extra plumbing.
The code
// app/routes/signup.tsx
import { json, redirect, ActionFunctionArgs } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
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;
}
export async function action({ request }: ActionFunctionArgs) {
const form = await request.formData();
const email = String(form.get('email') ?? '');
const password = String(form.get('password') ?? '');
if (await isDisposable(email)) {
return json(
{ error: 'Please use a real email address.' },
{ status: 400 }
);
}
// ...create user, set session, etc.
return redirect('/dashboard');
}
export default function SignUp() {
const data = useActionData<typeof action>();
return (
<Form method="post">
<input name="email" type="email" required />
<input name="password" type="password" required />
{data?.error && <p role="alert">{data.error}</p>}
<button type="submit">Sign up</button>
</Form>
);
}Notes
- Why in the action
- Putting the check inside the action means the form is the only entry point. No accidental API endpoint that bypasses the check.
- Progressive enhancement
- Remix Forms work without JS. The disposable check runs on the server regardless of whether the client hydrates — important for accessibility.
- Use with auth libraries
- If you use remix-auth, drop the same check into your Strategy's verify function. The action-based approach above works without any auth library.
Get a free API key
500 checks/month, no credit card. No credit card. 30 seconds.
Sign up free →