Node.js
Getting Started
You don't need any external dependencies to integrate user.cleaning with Node.js. We'll use Node.js's built-in https module to communicate with the API, keeping your application lightweight.
First, store your API key securely in your environment variables:
USER_CLEANING_API_KEY=your-api-keyNext, load it in your application:
const USER_CLEANING_API_KEY = process.env.USER_CLEANING_API_KEY || '';
module.exports = { USER_CLEANING_API_KEY };If you don't have an API key yet, check out our Quickstart guide to generate one.
IP Forwarding for Rate Limiting
For user.cleaning's rate limiting to work correctly, you need to forward the real client IP with each request. Add this helper function to extract the client IP:
function getClientIP(req) {
const forwarded = req.headers['x-forwarded-for'];
if (forwarded) {
return forwarded.split(',')[0].trim();
}
return req.headers['x-real-ip'] || req.socket.remoteAddress || 'unknown';
}
module.exports = { getClientIP };Production: If you're behind nginx, Cloudflare, or a load balancer, X-Forwarded-For is set automatically.
Development/testing: Without a reverse proxy forwarding X-Forwarded-For, the integration will see your server's IP instead of actual client IPs. This means all requests appear to come from the same IP, and after 5 disposable email attempts, all registrations will be blocked until the IP is unbanned.
If you experience unexpected registration blocks, verify your reverse proxy is forwarding client IPs correctly. You can unban blocked IPs at API Settings and configure number of registration attempts before block or disable it completely at Color Configuration.
Creating the Validation Function
Let's create a helper function that handles communication with the user.cleaning API:
const https = require('https');
const { USER_CLEANING_API_KEY } = require('./config');
async function checkEmail(email, clientIP) {
return new Promise((resolve) => {
const options = {
hostname: 'api.user.cleaning',
path: `/v1/external-api-requests/check-email?email=${encodeURIComponent(email)}`,
method: 'POST',
headers: {
'X-API-Key': USER_CLEANING_API_KEY,
'X-Forwarded-For': clientIP,
'X-Real-IP': clientIP,
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 403) {
const json = JSON.parse(data);
resolve({ category: 'banned', message: json.detail || 'Access denied' });
return;
}
resolve(JSON.parse(data));
});
});
req.on('error', () => resolve({ category: 'white' })); // Fail open
req.end();
});
}
module.exports = { getClientIP, checkEmail };The API responds with a JSON object containing a category field that tells you how to handle the email address.
Integration Approaches
Validating in Route Handlers
The most straightforward approach is to call the validation function directly in your route handlers:
const { getClientIP, checkEmail } = require('./utils');
app.post('/register', async (req, res) => {
const { email, password } = req.body;
const clientIP = getClientIP(req);
const result = await checkEmail(email, clientIP);
if (result.category === 'banned') {
return res.status(403).json({ error: result.message });
}
if (result.category === 'black') {
return res.status(400).json({ error: 'Disposable emails are not allowed' });
}
if (result.category === 'grey') {
// Suspicious email — let them in but require phone verification
const user = await createUser(email, password, { requiresPhoneVerification: true });
return res.status(201).json({
message: 'Please enter your phone number for verification',
userId: user.id
});
}
// 'white' — all good
const user = await createUser(email, password, { requiresPhoneVerification: false });
res.status(201).json({ userId: user.id });
});This approach is perfect when you only have one or two routes that need email validation and want explicit control over the logic.
Creating Reusable Middleware
If you need to validate emails across multiple routes, creating a middleware function keeps your code DRY and consistent:
const { getClientIP, checkEmail } = require('./utils');
async function validateEmail(req, res, next) {
const email = req.body.email;
if (!email) {
return res.status(400).json({ error: 'Email is required' });
}
const clientIP = getClientIP(req);
const result = await checkEmail(email, clientIP);
if (result.category === 'banned') {
return res.status(403).json({ error: result.message });
}
if (result.category === 'black') {
return res.status(400).json({ error: 'Disposable emails are not allowed' });
}
// Attach result for use in route handler
req.emailValidation = result;
next();
}
module.exports = { validateEmail };Now you can easily apply this validation to any route:
const { validateEmail } = require('./middleware');
app.post('/register', validateEmail, async (req, res) => {
const requiresVerification = req.emailValidation.category === 'grey';
const user = await createUser(req.body.email, req.body.password, { requiresVerification });
res.status(201).json({ userId: user.id });
});Understanding the Results
The category field can have three possible values:
- black — This is a disposable or temporary email address that should be blocked
- grey — Suspicious patterns were detected (like alias chains or plus addressing), consider requiring additional verification
- white — The email looks legitimate and can be accepted without restrictions
For a complete description of the output data structure and all available fields, see our Categories Reference.