Key Takeaways
- Node.js Express middleware provides the cleanest integration pattern for email verification, intercepting requests before they reach your route handler.
- The EmailVerifierAPI v2 endpoint accepts a simple GET request and returns structured JSON in under 300ms, fast enough for real-time form validation.
- The response includes status codes (passed, failed, unknown, transient) and boolean flags for disposable, role-based, free service, gibberish, and offensive addresses.
- Production implementations should include timeout handling, graceful degradation when the API is unreachable, and caching to avoid duplicate calls.
- The middleware pattern works with any Express-based framework including NestJS, and adapts easily to Fastify or Koa.
Node.js developers building applications that accept email addresses during registration, lead capture, or contact form submission need server-side validation that goes beyond regex pattern matching. The email-validator npm package checks formatting, and some libraries perform basic MX lookups, but neither can confirm whether a specific mailbox exists, whether the address belongs to a disposable email service, or whether it will actually accept delivery.
This guide walks through building Express middleware that performs real-time email verification using the EmailVerifierAPI v2 endpoint. The middleware pattern is the most idiomatic approach for Express applications, allowing you to add verification to any route with a single line of code.
Prerequisites and Setup
You need Node.js 18+ (for native fetch) and an API key. Register for 100 free email verification credits to get started. Store your key in an environment variable.
First, confirm the API with a curl test:
Building the Verification Middleware
The middleware intercepts incoming requests, extracts the email from the request body, verifies it against the API, and either passes the request to the next handler or returns an error response.
The middleware follows three critical production patterns. First, it includes a 10-second timeout using AbortSignal.timeout() to prevent hanging requests. Second, it degrades gracefully when the API is unreachable, allowing the signup to proceed rather than blocking users. Third, it attaches the verification result to the request object so downstream handlers can access flags like isRoleAccount or isFreeService for additional logic.
Wiring the Middleware to Routes
Adding verifyEmail to any route is a single argument in the route definition. No changes to the route handler are needed. This makes it trivial to add verification to existing routes or remove it for testing.
sub_status field provides granular detail like mailboxDoesNotExist, domainDoesNotExist, and isCatchall that help you build nuanced acceptance policies.
Understanding the Response
The API returns a JSON object with these key fields:
status:passed(deliverable),failed(undeliverable),unknown(inconclusive, often catch-all),transient(temporary error, retry later)sub_status: Granular reason includingmailboxExists,mailboxDoesNotExist,mailboxIsFull,domainDoesNotExist,isCatchall,isGreylistingisDisposable: Boolean flag for temporary email servicesisRoleAccount: Boolean flag for department addresses (info@, sales@)isFreeService: Boolean flag for Gmail, Yahoo, OutlookisGibberish: Boolean flag for random-character usernamessmtp_check:successorerrorindicating SMTP-level result
Batch Verification for List Cleaning
For cleaning existing user databases or processing imported lists, the same API works in a batch processing loop. Iterate through addresses with a small delay between calls to respect rate limits.
Read addresses from your database or CSV file, verify each one sequentially with a 150ms delay, and write the results back with all verification flags included. A simple loop processing at 6 requests per second can verify approximately 21,600 addresses per hour. For larger datasets, run multiple processes in parallel.
After batch verification completes, analyze the result distribution. A typical user database will show 75-85% passed, 5-15% failed, 3-8% unknown, and 1-3% disposable. Use this data to build suppression segments and clean up your active sending lists. Addresses with status: failed should be immediately removed from all marketing and transactional sequences.
For NestJS applications, create a verification service using the @Injectable() decorator and inject it into your controllers or guards. The HTTP call pattern is identical to the Express middleware, wrapped in NestJS dependency injection conventions. The middleware can also be implemented as a NestJS Guard for route-level protection.
For applications built on Fastify or Koa, adapt the middleware to the respective plugin or middleware patterns. The core verification logic (HTTP call, response parsing, decision logic) remains the same regardless of framework. Only the middleware registration syntax changes.
For TypeScript projects, add type annotations to the middleware function signature and the verification result. Define an interface for the API response that includes all fields (status, sub_status, isDisposable, isRoleAccount, isFreeService, isGibberish, isOffensive, smtp_check). This provides compile-time safety and autocompletion in your IDE when accessing verification flags downstream.
Consider creating a verification result type that you attach to the Express Request object using declaration merging. This way, route handlers that execute after the middleware can access req.emailVerification with full type safety, eliminating runtime errors from misspelled property names.
For high-traffic applications processing hundreds of signups per hour, implement connection pooling for the verification API calls. While the native fetch API handles this automatically in most environments, explicit pool management becomes important when running in containerized environments or behind load balancers where connection reuse behavior may differ from development.
Advanced Response Handling Patterns
The basic middleware shown above handles the most common cases: rejecting failed addresses and blocking disposable emails. In production, you will want more nuanced handling based on the full response payload.
Catch-all domain handling: When sub_status returns isCatchall, the address cannot be definitively verified because the domain accepts all incoming email. Accept these addresses provisionally but tag them in your database for monitoring. If a catch-all address generates a bounce on the first real send, suppress it immediately.
Free email provider detection: For B2B applications, the isFreeService flag identifies Gmail, Yahoo, and Outlook addresses. You might accept these for consumer signups but require a company domain for enterprise trial accounts. The middleware can branch based on this flag to apply different registration flows.
Gibberish and offensive detection: The isGibberish flag catches bot-generated usernames (like xk29fj4@gmail.com), while isOffensive catches inappropriate content. Both flags help filter out accounts that would never engage with your product, protecting your engagement metrics and brand safety.
Role-based address routing: When isRoleAccount is true (info@, sales@, support@), accept the address for account creation but exclude it from marketing sequences. Tag it in your CRM as transactional-only to prevent it from being enrolled in drip campaigns that would generate complaints.
Production Hardening
API key security: Never hardcode your API key. Use process.env.EVA_API_KEY and store the value in .env (with dotenv) or your deployment platform''s secrets manager. Add .env to .gitignore.
Caching: For forms where users re-submit the same email (correction flow), cache results in memory or Redis with a 5-minute TTL to avoid duplicate API calls. Use the email as the cache key.
Retry for transient: When the API returns status: transient, implement exponential backoff (1s, 2s, 4s) up to 3 retries. Never reject a user based on a transient result.
Logging: Log verification outcomes (without the API key) to monitor your pass/fail distribution and credit consumption. This data helps you tune acceptance policies and forecast API costs. See email verification pricing for volume-based rates.
For additional language integrations, visit the email verification integrations hub for PHP, Python, Go, Java, and more.
Frequently Asked Questions
What Node.js version is required?
Node.js 18+ is recommended for native fetch support. For Node.js 16, install node-fetch as a polyfill. The middleware pattern works identically with either approach. There are no other runtime dependencies beyond Express.
Will the middleware slow down my registration flow?
The API responds in under 300ms for most addresses. Combined with network latency, total middleware execution is typically 300-500ms, comparable to other real-time validations like reCAPTCHA or payment processing. For forms where this delay is noticeable, display a loading indicator during verification.
How do I handle verification in a serverless environment?
The same verification logic works in AWS Lambda, Vercel, or Cloudflare Workers. Replace the Express middleware pattern with a function call at the beginning of your serverless handler. The API is stateless, so there is no connection pooling or session management to worry about.