El rebote más barato es el que nunca llega a entrar en tu lista. Verificar una dirección en el momento en que alguien la escribe —en una función serverless detrás de tu formulario de registro— frena erratas, dominios muertos y direcciones desechables en la puerta, en lugar de pagar por limpiarlas más tarde y encajar el golpe de entregabilidad entretanto. Esta guía muestra el patrón con una ruta serverless funcional.
La respuesta corta#
Pon la verificación en una función del lado del servidor a la que llame tu
formulario de registro: guarda tu clave de API, llama a POST /verify
y actúa sobre el veredicto local inmediato: rechaza undeliverable, ofrece la
corrección did_you_mean, marca disposable y deja pasar todo lo demás. Dos
reglas lo hacen funcionar o lo arruinan: nunca llames a la API desde el navegador
(la clave debe quedarse en el servidor) y nunca bloquees un registro cuando la
API esté lenta o caída (fail open).
Por qué en el registro y no después#
La verificación en la captura es prevención; limpiar una lista es reparación, y la prevención gana en todos los ejes. Una errata atrapada en el teclado es un suscriptor salvado con una corrección de una línea. Un dominio muerto rechazado en el registro nunca se convierte en un rebote duro que abolla tu tasa de rebote. Una dirección desechable bloqueada en la puerta nunca diluye tus métricas ni tu factura. La misma dirección encontrada en una limpieza de lista tres meses después ya te ha costado un envío, un rebote y una brizna de reputación de remitente.
El patrón#
Tres piezas en movimiento:
- El formulario envía la dirección a tu propio endpoint, nunca directamente a la API de verificación, porque eso expondría tu clave.
- Una función serverless guarda la clave en una variable de entorno, llama a
POST /verifyy convierte el resultado en una decisión. - Tu lógica de registro actúa sobre la decisión: rechazar, sugerir una corrección o aceptar.
Una ruta serverless#
Aquí la tienes como un Route Handler del App Router de Next.js, que se despliega
como una función serverless en Vercel. La clave vive en QUALISEND_API_KEY; el
cliente nunca la ve:
// app/api/validate-email/route.ts
import { NextResponse } from "next/server";
const BASE = "https://app.qualisend.com/api/v1";
export async function POST(req: Request) {
const { email } = await req.json();
try {
const res = await fetch(`${BASE}/verify`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.QUALISEND_API_KEY!}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(4000), // don't let a slow probe stall signup
});
// On any API error, fail open — a real user must not be blocked by our outage.
if (!res.ok) return NextResponse.json({ ok: true, degraded: true });
const { result } = await res.json();
if (result.status === "undeliverable") {
return NextResponse.json({
ok: false,
reason: result.reason, // invalid_email | invalid_domain | rejected_email
suggestion: result.did_you_mean, // e.g. "jane@gmail.com"
});
}
if (result.sub_flags?.disposable) {
return NextResponse.json({ ok: false, reason: "disposable" });
}
// deliverable, risky, or unknown — accept, but pass along a typo suggestion if any
return NextResponse.json({ ok: true, status: result.status, suggestion: result.did_you_mean });
} catch {
return NextResponse.json({ ok: true, degraded: true }); // timeout or network error → fail open
}
}
Guarda la clave con las herramientas de entorno de tu plataforma
(vercel env add QUALISEND_API_KEY) y se inyecta en tiempo de ejecución, nunca se
empaqueta en el cliente.
Actuar sobre el veredicto#
El result inmediato es el veredicto local (sintaxis, DNS, desechable, rol,
errata), que es exactamente lo que quieres en el registro: rápido y suficiente
para decidir:
| Resultado | Acción en el registro |
|---|---|
undeliverable | Rechaza en línea: "esa dirección no parece entregable". |
did_you_mean presente | Ofrece la corrección —"¿quisiste decir jane@gmail.com?"— la recuperación de mayor valor. |
indicador disposable | Rechaza o marca, según lo estricto que sea tu producto; consulta de rol, desechables y gratuitas. |
risky / catch-all | Acepta, pero etiquétala para poder enviar al segmento catch-all con cuidado. |
unknown | Acepta. La infraestructura no respondió; nunca pierdas un registro real por ello. |
deliverable | Acepta. |
Fail open, siempre#
Lo único innegociable: si la llamada de verificación falla, agota el tiempo de
espera o devuelve un código distinto de 2xx, deja pasar el registro. Una API de
verificación es un filtro de calidad, no una barrera de autenticación, y bloquear
a un cliente de pago por una caída transitoria es un resultado mucho peor que
dejar entrar una dirección dudosa. Tanto la rama !res.ok como el catch de
arriba devuelven ok: true exactamente por esta razón. Registra los registros
degradados y atrápalos en tu próxima
limpieza de lista.
Preguntas frecuentes#
¿Verificar en el registro ralentiza el formulario?#
Apenas, si lo haces bien. El veredicto local se devuelve rápido, y el timeout de 4 segundos de arriba acota el peor caso, pasado el cual aplicas el fail open y aceptas el registro de todos modos. Los usuarios reciben correcciones "¿quisiste decir…?" casi instantáneas; nunca esperan a un servidor de correo lento, porque estás actuando sobre el resultado local inmediato, no sondeando la sonda SMTP.
¿Debería bloquear las direcciones de email desechables en el registro?#
Depende de tu producto. Para un servicio de pago o sensible a la reputación, bloquear las desechables en la puerta merece la pena: nunca tuvieron intención de volver a saber de ti. Para un registro de consumidor con poca fricción, marcarlas para una revisión posterior puede ser mejor que añadir fricción. La ruta de arriba las rechaza; suavízala a un simple marcado si eso encaja con tu embudo.
¿Necesito el resultado completo confirmado por SMTP en el registro?#
Normalmente no. El veredicto local inmediato atrapa el grueso de los registros
malos —erratas, dominios muertos, desechables— sin esperas. Reserva el resultado
completo confirmado por SMTP (mediante GET /jobs o un job masivo)
para la limpieza de listas, donde la latencia no importa y sí importa atrapar cada
buzón muerto.
¿Cómo mantengo en secreto mi clave de API?#
Llama a la API de verificación solo desde código del lado del servidor —una función serverless, un route handler o un backend— y guarda la clave en una variable de entorno, nunca en JavaScript del lado del cliente. Si la clave acabaría en el bundle del navegador, está en el lugar equivocado.
¿Listo para conectarlo? El plan gratuito incluye 100 créditos para
probar el flujo, y la referencia de la API tiene el endpoint
/verify con ejemplos listos para copiar y pegar en siete lenguajes.