How to Block Disposable Emails with an API (PHP, Node.js & Python Examples)
May 20, 2026
Disposable email addresses are the single biggest enabler of signup fraud. Services like Mailinator, Guerrilla Mail, and Temp Mail let anyone generate a throwaway inbox in seconds, use it to create an account on your platform, and never look back.
If you run a SaaS product with a free tier, a trial period, or any kind of per-user benefit, disposable emails are costing you money right now. This guide walks through exactly how to block them using an API, with working code examples in PHP, Node.js, and Python.
Why Disposable Emails Are a Problem
For a deeper overview, see our full guide on what disposable emails are and why you should block them.
The short version: disposable email addresses let bad actors:
- Farm free trials by creating unlimited accounts, each with a fresh throwaway address
- Abuse promotional offers like signup credits, referral bonuses, and discount codes
- Pollute your user database with accounts that will never convert, skewing your metrics
- Bypass email verification since the temporary inbox does receive mail long enough to click a confirmation link
- Avoid accountability by making it impossible to contact or identify the user later
There are over 5,000 known disposable email domains, and new ones appear daily. Maintaining your own blocklist is a losing battle. An API that is continuously updated is the practical solution.
Choosing an Email Validation API
You need an API that checks email addresses in real time and returns a clear signal about whether the domain is disposable. Key features to look for:
- Disposable domain detection with a continuously updated database
- DNS and MX record validation to catch domains that don't actually receive mail
- Risk scoring that considers multiple factors beyond just the domain
- Low latency so your signup flow stays fast (under 200ms)
- Clear, consistent response format that is easy to integrate
Fidro's email validation API covers all of these in a single request. It checks the email against a database of over 5,000 disposable domains, validates DNS records, and returns a risk score alongside individual flags.
You can test any email address instantly with the free email checker tool before writing any code.
API Overview
Every example in this guide calls the same endpoint:
POST https://api.fidro.io/v1/validate
With a JSON body containing at minimum an email parameter:
{
"email": "user@tempmail.com"
}
The response includes fields like disposable, valid, risk_score, and mx_valid:
{
"email": "user@tempmail.com",
"valid": true,
"disposable": true,
"mx_valid": true,
"risk_score": 85,
"domain": "tempmail.com",
"reason": "Disposable email domain detected"
}
PHP / Laravel Implementation
Laravel makes this clean with its HTTP client and form request validation.
Basic Controller Check
use Illuminate\Support\Facades\Http;
class RegisterController extends Controller
{
public function store(Request $request)
{
$request->validate([
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
// Check for disposable email
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.fidro.api_key'),
])->timeout(5)->post('https://api.fidro.io/v1/validate', [
'email' => $request->email,
]);
if ($response->successful() && $response->json('disposable')) {
return back()->withErrors([
'email' => 'Please use a permanent email address. Temporary or disposable emails are not accepted.',
]);
}
$user = User::create([
'email' => $request->email,
'password' => Hash::make($request->password),
]);
return redirect()->route('dashboard');
}
}
Custom Validation Rule (Reusable)
For a cleaner approach, create a custom validation rule you can reuse across your application:
// app/Rules/NotDisposableEmail.php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class NotDisposableEmail implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$domain = strtolower(substr(strrchr($value, '@'), 1));
$isDisposable = Cache::remember(
"fidro:disposable:{$domain}",
now()->addHours(24),
function () use ($value) {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.fidro.api_key'),
])->timeout(5)->post('https://api.fidro.io/v1/validate', [
'email' => $value,
]);
if ($response->failed()) {
return false;
}
return $response->json('disposable', false);
}
);
if ($isDisposable) {
$fail('Please use a permanent email address. Disposable emails are not accepted.');
}
}
}
Then use it in any form request:
$request->validate([
'email' => ['required', 'email', 'unique:users', new NotDisposableEmail],
]);
For a more detailed Laravel walkthrough, see our guide on adding email validation to Laravel.
Node.js / Express Implementation
const express = require('express');
const router = express.Router();
const FIDRO_API_KEY = process.env.FIDRO_API_KEY;
async function checkDisposableEmail(email) {
const response = await fetch('https://api.fidro.io/v1/validate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${FIDRO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (!response.ok) {
console.error(`Fidro API error: ${response.status}`);
return { disposable: false, valid: true };
}
return response.json();
}
router.post('/signup', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
try {
const validation = await checkDisposableEmail(email);
if (validation.disposable) {
return res.status(422).json({
error: 'Please use a permanent email address. Disposable emails are not accepted.',
});
}
if (!validation.valid) {
return res.status(422).json({
error: 'This email address appears to be invalid. Please check and try again.',
});
}
const user = await createUser(email, password);
return res.status(201).json({ user });
} catch (err) {
console.error('Signup error:', err);
return res.status(500).json({ error: 'Something went wrong' });
}
});
Adding a Cache Layer in Node.js
const NodeCache = require('node-cache');
const emailCache = new NodeCache({ stdTTL: 86400 });
async function checkDisposableEmailCached(email) {
const domain = email.split('@')[1].toLowerCase();
const cached = emailCache.get(domain);
if (cached !== undefined) {
return cached;
}
const result = await checkDisposableEmail(email);
emailCache.set(domain, result);
return result;
}
For more on building middleware around this, check out our post on building fraud prevention middleware in Node.js.
Python / Flask Implementation
import os
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
FIDRO_API_KEY = os.environ.get('FIDRO_API_KEY')
def check_disposable_email(email: str) -> dict:
try:
response = requests.post(
'https://api.fidro.io/v1/validate',
headers={
'Authorization': f'Bearer {FIDRO_API_KEY}',
'Content-Type': 'application/json',
},
json={'email': email},
timeout=5,
)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
app.logger.error(f'Fidro API error: {e}')
return {'disposable': False, 'valid': True}
@app.route('/signup', methods=['POST'])
def signup():
data = request.get_json()
email = data.get('email', '').strip().lower()
password = data.get('password', '')
if not email or not password:
return jsonify({'error': 'Email and password are required'}), 400
validation = check_disposable_email(email)
if validation.get('disposable'):
return jsonify({
'error': 'Please use a permanent email address. Disposable emails are not accepted.'
}), 422
if not validation.get('valid'):
return jsonify({
'error': 'This email address appears to be invalid.'
}), 422
user = create_user(email, password)
return jsonify({'user': user}), 201
Caching Strategy
Cache by domain, not by full email address. If user1@tempmail.com is disposable, then user2@tempmail.com is also disposable. Domain-level caching dramatically reduces API calls.
Use a 24-hour TTL. Disposable domain databases update daily. A 24-hour cache window balances freshness with API efficiency.
Fail open, not closed. If the API is down or times out, allow the signup to proceed. Flag the account for async re-validation later.
// Fail-open pattern in Laravel
try {
$response = Http::timeout(5)->post('https://api.fidro.io/v1/validate', [
'email' => $email,
]);
if ($response->failed()) {
Log::warning('Fidro API unavailable, allowing signup', ['email' => $email]);
return true;
}
return !$response->json('disposable', false);
} catch (\Exception $e) {
Log::warning('Fidro API timeout, allowing signup', ['email' => $email]);
return true;
}
Handling Edge Cases
Free Email Providers vs. Disposable Emails
Gmail, Yahoo, and Outlook are free but legitimate. Fidro's response includes a separate free_provider flag alongside the disposable flag, so you can treat them differently. For a deeper dive, see free email providers vs. disposable emails.
What to Tell the User
Never tell a blocked user that their "email is disposable." Instead, use a neutral message:
- "Please use a permanent email address to create your account."
- "We could not verify this email address. Please try a different one."
Combining Email and IP Validation
For maximum protection, validate both the email and the IP address in a single call:
curl -X POST https://api.fidro.io/v1/validate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@tempmail.com",
"ip": "185.234.72.19"
}'
This gives you disposable email detection, IP risk analysis (VPN, proxy, Tor, data centre detection), and an overall risk score in a single API call. Check our IP checker tool and VPN detector to see this in action.
Next Steps
- Get your API key at fidro.io/pricing. The free tier includes 200 validations per month.
- Integrate the validation into your signup flow using the code examples above.
- Set up caching by domain to reduce API calls and improve response times.
- Monitor your results by tracking how many disposable emails are blocked.
For the full API reference, visit the Fidro documentation. Disposable emails are a solvable problem. A single API call at signup is all it takes to stop the vast majority of throwaway accounts before they ever reach your database.