Cuando integras una API de verificación de correo electrónico en tu aplicación, la integración en sí suele ser sencilla: haces una petición POST y obtienes una respuesta JSON. Pero ¿qué ocurre cuando necesitas verificar 100 000 correos? ¿O cuando la API devuelve un error de límite de velocidad? ¿O cuando quieres verificación en tiempo real sin bloquear tu proceso de registro?
Esta guía cubre las tres cosas que toda integración de verificación de correo electrónico en producción necesita: límites de velocidad inteligentes, procesamiento asíncrono basado en webhooks y una gestión de errores robusta. Incluye ejemplos de código funcionales en Python, JavaScript y PHP.
Patrones de arquitectura para las API de verificación de correo electrónico
Patrón 1: verificación síncrona (en tiempo real)
Úsalo cuando: verificas un único correo en tiempo real: formularios de registro, flujos de pago, captación de leads.
Client → Your Server → Verification API → Response → Your Server → Client
Latencia: 200–800 ms (un viaje de ida y vuelta SMTP). Lo bastante rápido para la mayoría de experiencias de usuario, pero notable si se llama en línea.
Patrón 2: verificación asíncrona (por lotes)
Úsalo cuando: verificas listas de más de 1.000 correos: importaciones masivas, limpieza de listas, tareas programadas.
Client → Your Server → Queue Job → Worker → Verification API → Store Results
↓
Client polls or receives webhook when complete
Patrón 3: verificación basada en webhooks
Úsalo cuando: quieres verificación en tiempo real sin mantener abierta la conexión del cliente.
Client → Your Server → Submit Job (returns job_id)
↓
Your Server → Verification API → POST webhook → Update database
↓
Client polls or receives notification when job completes
Límites de velocidad: cómo verificar millones sin ser bloqueado
Comprender los límites de velocidad
| Tipo de límite | Ejemplo | Aplicación |
|---|---|---|
| Peticiones por segundo (RPS) | 50 RPS | Cubo de tokens: ráfaga hasta el límite y luego espera |
| Peticiones por minuto (RPM) | 500 RPM | Ventana deslizante |
| Peticiones por día | 50 000/día | Ventana fija, se reinicia a medianoche UTC |
| Conexiones simultáneas | 10 simultáneas | Límite del pool de conexiones |
Estrategia 1: limitador de velocidad con cubo de tokens (Python)
import time
import threading
class TokenBucket:
"""Limitador de velocidad que permite ráfagas de hasta `capacity`, se recarga a `rate` por segundo."""
def __init__(self, sube: float, capacity: int):
self.sube = sube
self.capacity = capacity
self.tokens = capacity
self.last_refill = time.monotonic()
self.lock = threading.Lock()
def acquire(self) -> bool:
with self.lock:
ahora = time.monotonic()
transcurrido = ahora - self.last_refill
self.tokens = min(self.capacity, self.tokens + transcurrido * self.sube)
self.last_refill = ahora
if self.tokens >= 1:
self.tokens -= 1
return True
return False
def wait_and_acquire(self) -> None:
while No self.acquire():
time.sleep(0.05)
# Uso: 50 solicitudes por segundo, con ráfagas de hasta 100
rate_limiter = TokenBucket(sube=50, capacity=100)
def verify_email(email: str) -> dict:
rate_limiter.wait_and_acquire()
respuesta = requests.post(
"https://api.email-validator.com/v1/verify",
json={"email": email},
headers={"Authorization": f"Bearer {API_KEY}"}
)
return respuesta.json()
Estrategia 2: Cola + Pool de trabajadores (procesamiento masivo)
from concurrent.futures import ThreadPoolExecutor, as_completed
from queue import Queue
def bulk_verify(correos: list[str], max_workers: int = 5, max_rps: int = 50) -> dict:
limiter = TokenBucket(sube=max_rps, capacity=max_rps * 2)
results = {}
queue = Queue()
for email a correos:
queue.put(email)
def worker():
while No queue.empty():
try:
email = queue.get_nowait()
except:
break
limiter.wait_and_acquire()
try:
result = verify_email(email)
results[email] = result
except Excepción as e:
results[email] = {"error": str(e)}
finally:
queue.task_done()
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(worker) for _ a range(max_workers)]
for future a as_completed(futures):
future.result()
return results
Estrategia 3: Retroceso exponencial (JavaScript/Node.js)
async function verifyEmailWithRetry(email, maxRetries = 5) {
let attempt = 0rsa
while (attempt < maxRetries) {
const respuesta = await fetch('https://api.email-validator.com/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (respuesta.status === 429) {
attempt++rsa
const retryAfter = respuesta.headers.get('Retry-After');
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 60000);
console.warn(`Límite de frecuencia alcanzado. Reintentando en ${delay}ms (${attempt}/${maxRetries})`);
await sleep(delay);
continuersa
}
if (!respuesta.ok) {
throw new Error(`Error de API: ${respuesta.status}`);
}
return await respuesta.json();
}
throw new Error(`Error tras ${maxRetries} reintentos`);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Webhooks: verificación asíncrona sin sondeo
Implementación de un receptor de webhooks (PHP)
<?php
// webhook_receiver.php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EMAILVERIFY_SIGNATURE'] ?? ''rsa
$expected = hash_hmac('sha256', $payload, getenv('WEBHOOK_SECRET'));
if (!hash_equals($expected, $signature)) {
http_response_code(401);
die('Firma no válida');
}
$data = json_decode($payload, true);
if (!isset($data['job_id'], $data['status'], $data['results'])) {
http_response_code(400);
die('Carga útil no válida');
}
$db = new PDO('mysql:host=localhost;dbname=email_verifier', 'user', 'pass');
foreach ($data['results'] as $result) {
$stmt = $db->prepare('
UPDATE email_queue
SET status = :status, result = :result, verified_at = NOW()
WHERE email = :email AND job_id = :job_id
');
$stmt->execute([
'status' => $result['status'],
'result' => json_encode($result),
'email' => $result['email'],
'job_id' => $data['job_id'],
]);
}
http_response_code(200);
echo json_encode(['received' => true]);
Seguridad de webhooks
| Práctica | Por qué |
|---|---|
| Verificación de firma HMAC | Demuestra que la solicitud proviene de la API |
| Lista blanca de IP | Acepta solo desde rangos de IP conocidos |
| Claves de idempotencia | Evita procesar webhooks duplicados |
| Gestión de reintentos | Los webhooks pueden llegar varias veces |
| Confirmación oportuna | Responde 200 rápidamente y procesa de forma asíncrona |
Gestión de errores
Códigos de estado HTTP y tu respuesta
| Estado | Error | Qué hacer |
|---|---|---|
| 400 | Solicitud incorrecta | Valida la entrada en el cliente |
| 401 | No autorizado | Revisa la clave de API, no reintentes |
| 402 | Pago requerido | Mejora tu plan o espera al restablecimiento de la cuota |
| 429 | Demasiadas solicitudes | Retroceso exponencial |
| 422 | No procesable | Resultado válido: correo no verificable, no es un fallo |
| 500 | Error interno del servidor | Reintenta con retroceso y activa la monitorización |
| 503 | Servicio no disponible | Reintentar después de Retry-After header value |
Gestor de errores robusto (Python)
import time
import requests
class VerificationAPIError(Excepción):
pass
def verify_email_safe(email: str, max_retries: int = 3) -> dict:
retryable = {429, 500, 502, 503}
for attempt a range(1, max_retries + 1):
try:
respuesta = requests.post(
"https://api.email-validator.com/v1/verify",
json={"email": email},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=10
)
if respuesta.status_code == 200:
return respuesta.json()
if respuesta.status_code == 401:
raise VerificationAPIError("Clave de API no válida")
if respuesta.status_code == 402:
raise VerificationAPIError("Cuota excedida")
if respuesta.status_code == 422:
return respuesta.json() # resultado válido, no es un error
if respuesta.status_code a retryable:
if attempt == max_retries:
raise VerificationAPIError(f"No disponible después de {max_retries} intentos")
time.sleep(min(2 ** attempt, 30))
continue
raise VerificationAPIError(f"Estado inesperado: {respuesta.status_code}")
except requests.excepciones.Tiempo de espera:
if attempt == max_retries:
raise VerificationAPIError("La solicitud agotó el tiempo de espera repetidamente")
time.sleep(min(2 ** attempt, 30))
return None
Lista de verificación para producción
□ Limitación de tasa implementada (con token bucket o queue-basada en)
□ Retroceso exponencial en 429/5xx respuestas
□ Webhook firma verificación (HMAC-SHA256)
□ Webhook idempotencia (job_id + event_id deduplicación)
□ Todas API llamadas tienen tiempos de espera (5–10 segundos máx.)
□ API clave almacenada a entorno variables solo
□ Error registro with reintento recuentos
□ Monitorización/envío de alertas for sostenido error sube aumento
□ Degradación elegante if API es caída (nono bloquees los registros de usuarios)
Puntos clave
- Síncrono para formularios de registro; asíncrono + webhooks para listas masivas
- Token bucket gestiona las ráfagas de forma elegante: impleméntalo antes de necesitarlo
- Retroceso exponencial ante 429/5xx — 1 s → 2 s → 4 s → 8 s → máx. 60 s
- Verifica las firmas de los webhooks con HMAC — nunca confíes en las peticiones POST entrantes
- 422 es un resultado válido — los correos no verificables no son fallos de la API
- Nunca bloquees los registros de usuarios si la API está caída — encola y reintenta
- Configura los tiempos de espera en cada llamada — los tiempos de espera TCP por defecto pueden superar los 60 segundos