Construcción de una herramienta de validación de destinatarios de aves asíncrona masiva
Zacarías Samuels
26 may 2022
Correo electrónico
1 min read

Puntos clave
The author built a bulk recipient validation tool to validate millions of email addresses efficiently using Bird’s Recipient Validation API.
Node.js proved faster and more scalable than Python due to its non-blocking I/O and lack of GIL limitations.
The tool reads CSV files asynchronously, calls the validation API for each email, and writes results to a new CSV in real time.
The approach avoids memory bottlenecks and improves throughput to about 100,000 validations in under a minute.
Future improvements could include better retry handling, a user-friendly UI, or migrating to serverless environments for scalability.
Q&A Highlights
What is the purpose of the Bulk Asynchronous Recipient Validation Tool?
It validates large volumes of email addresses by integrating directly with Bird’s Recipient Validation API, outputting verified results quickly without manual uploads.
Why was Python initially used and later replaced by Node.js?
Python’s Global Interpreter Lock (GIL) limited concurrency, while Node.js allowed true asynchronous execution, resulting in far faster parallel API calls.
How does the tool handle large files without running out of memory?
Instead of loading all data at once, the script processes each CSV line individually—sending the validation request and immediately writing results to a new CSV file.
What problem does the tool solve for developers?
It enables email list validation at scale, overcoming the 20MB limit of SparkPost’s UI-based validator and eliminating the need to upload multiple files manually.
How fast is the final version of the program?
Around 100,000 validations complete in 55 seconds, compared to over a minute using the UI version.
What issues were encountered on Windows systems?
Node.js HTTP client connection pooling caused “ENOBUFS” errors after many concurrent requests, which were fixed by configuring axios connection reuse.
What future enhancements are suggested?
Adding error handling and retries, creating a front-end interface, or implementing the tool as a serverless Azure Function for better scalability and resilience.
Para alguien que busca un programa simple y rápido que acepte un csv, llame a la API de validación de receptores y produzca un CSV, este programa es para ti.
Cuando se construyen aplicaciones de correo electrónico, los desarrolladores a menudo necesitan integrar múltiples servicios y APIs. Comprender los fundamentos de la API de correo electrónico en la infraestructura de nube proporciona la base para construir herramientas robustas como el sistema de validación masiva que crearemos en esta guía.
Una de las preguntas que recibimos ocasionalmente es, ¿cómo puedo validar masivamente listas de correo electrónico con validación de destinatarios? Hay dos opciones aquí, una es cargar un archivo a través de la interfaz de SparkPost para la validación, y la otra es hacer llamadas individuales por correo electrónico a la API (ya que la API es de validación de correo electrónico individual).
La primera opción funciona muy bien pero tiene una limitación de 20 Mb (alrededor de 500,000 direcciones). ¿Qué pasa si alguien tiene una lista de correos electrónicos que contiene millones de direcciones? Podría significar dividir eso en miles de cargas de archivos CSV.
Como cargar miles de archivos CSV parece un poco descabellado, tomé ese caso de uso y comencé a preguntarme qué tan rápido podría hacer que la API funcionara. En esta publicación del blog, explicaré lo que probé y cómo finalmente llegué a un programa que podría realizar 100,000 validaciones en 55 segundos (mientras que en la interfaz obtuve alrededor de 100,000 validaciones en 1 minuto y 10 segundos). Y aunque esto aún tomaría aproximadamente 100 horas parahacer cerca de 654 millones de validaciones, este script puede ejecutarse en segundo plano, ahorrando tiempo significativo.
La versión final de este programa se puede encontrar aquí.
Mi primer error: usar Python
Python es uno de mis lenguajes de programación favoritos. Sobresale en muchas áreas y es increíblemente sencillo. Sin embargo, un área en la que no sobresale es en procesos concurrentes. Aunque python tiene la capacidad de ejecutar funciones asincrónicas, tiene lo que se conoce como The Python Global Interpreter Lock o GIL.
“The Python Global Interpreter Lock o GIL, en palabras simples, es un mutex (o un bloqueo) que permite que solo un hilo tenga el control del intérprete de Python.
Esto significa que solo un hilo puede estar en un estado de ejecución en cualquier momento dado. El impacto del GIL no es visible para los desarrolladores que ejecutan programas de un solo hilo, pero puede ser un cuello de botella de rendimiento en código controlado por CPU y multihilo.
Ya que el Global Interpreter Lock (GIL) permite que solo un hilo se ejecute a la vez, incluso en sistemas multi-núcleo, ha ganado reputación como una característica “infame” de Python (ver el artículo de Real Python sobre el GIL).
Al principio, no estaba al tanto del GIL, así que comencé a programar en python. Al final, aunque mi programa era asincrónico, se estaba bloqueando, y no importaba cuántos hilos añadiera, aún así solo obtenía unas 12-15 iteraciones por segundo.
La parte principal de la función asincrónica en Python se puede ver a continuación:
Así que dejé de usar Python y volví a la mesa de dibujo…
Me decidí por utilizar NodeJS debido a su capacidad de realizar operaciones de i/o no bloqueantes extremadamente bien. Otra excelente opción para manejar el procesamiento asincrónico de API es construir consumidores de webhook sin servidor con Azure Functions, que pueden manejar eficientemente cargas de trabajo variables. También estoy bastante familiarizado con la programación en NodeJS.
Utilizando aspectos asincrónicos de Node.js, este enfoque funcionó bien. Para más detalles sobre la programación asincrónica en Node.js, ver la guía de RisingStack sobre programación asincrónica en Node.js.
Mi segundo error: intentar leer el archivo en memoria
Desglosando el código final
Después de leer y validar los argumentos del terminal, ejecuto el siguiente código. Primero, leo el archivo CSV de correos electrónicos y cuento cada línea. Hay dos propósitos para esta función, 1) me permite informar con precisión sobre el progreso del archivo [como veremos más adelante], y 2) me permite detener un temporizador cuando el número de correos electrónicos en el archivo es igual al de validaciones completadas. Agregué un temporizador para poder ejecutar benchmarks y asegurarme de obtener buenos resultados.
A continuación, llamo a la función validateRecipients. Nota que esta función es asincrónica. Después de validar que el infile y outfile son CSV, escribo una fila de encabezado y comienzo un temporizador del programa usando la biblioteca JSDOM.
El siguiente script es realmente la mayor parte del programa, así que lo dividiré y explicaré lo que está sucediendo. Para cada línea del infile:
Asíncronamente toma esa línea y llama a la API de validación de destinatarios.
Luego, en la respuesta
Agrega el correo electrónico al JSON (para poder imprimir el correo electrónico en el CSV)
Valida si la razón es nula, y si es así, rellena un valor vacío (esto es para que el formato CSV sea consistente, ya que en algunos casos se da una razón en la respuesta)
Establece las opciones y claves para el módulo json2csv.
Convierte el JSON a CSV y lo exporta (utilizando json2csv)
Escribe el progreso en el terminal
Finalmente, si el número de correos electrónicos en el archivo = validaciones completadas, detén el temporizador y muestra los resultados
Un último problema que encontré fue que, aunque esto funcionó muy bien en Mac, me encontré con el siguiente error usando Windows después de alrededor de 10,000 validaciones:
Error: connect ENOBUFS XX.XX.XXX.XXX:443 – Local (indefinido:indefinido) con correo electrónico XXXXXXX@XXXXXXXXXX.XXX
Después de investigar un poco más, parece ser un problema con el pool de conexiones del cliente HTTP de NodeJS que no reutiliza conexiones. Encontré este artículo de Stackoverflow sobre el problema, y tras investigar más, encontré una buena configuración por defecto para la biblioteca axios que resolvió este problema. Todavía no estoy seguro de por qué este problema solo sucede en Windows y no en Mac.
Siguientes pasos
Para alguien que está buscando un programa rápido y simple que tome un csv, llame a la API de validación de destinatarios y genere un CSV, este programa es para usted.
Algunas adiciones a este programa serían las siguientes:
Construir un front end o una interfaz de usuario más fácil de usar
Mejor manejo de errores y reintentos porque si por alguna razón la API arroja un error, el programa actualmente no reintenta la llamada
Considerar la implementación como una función de Azure sin servidor para escalamiento automático y gestión reducida de infraestructura
También me gustaría ver si se podrían lograr resultados más rápidos con otro lenguaje como Golang o Erlang/Elixir. Más allá de la elección del lenguaje, las limitaciones de infraestructura también pueden afectar el rendimiento - lo hemos aprendido de primera mano cuando enfrentamos límites de DNS no documentados en AWS que afectaron nuestros sistemas de procesamiento de correo electrónico de alto volumen.
Para desarrolladores interesados en combinar el procesamiento de API con herramientas de flujo de trabajo visual, descubran cómo integrar Flow Builder con Google Cloud Functions para flujos de trabajo de automatización sin código.
Por favor, no dude en proporcionarme cualquier comentario o sugerencia para expandir este proyecto.





