Cómo enviar email desde Cloudflare Workers
Un Worker no puede ejecutar un servidor de correo, pero sí puede hablar con uno. Esto es lo que funciona, lo que falla y por qué, aprendido al construir MakiSend, una API de email que corre por completo en Workers.
Última actualización · Versión en Markdown (en inglés)
Tres formas de enviar
Un Worker tiene tres salidas. Elige según de quién quieres depender, no según el código: el código es corto en todos los casos.
| Enfoque | Cómo envía el Worker | Qué necesitas | Cuidado con |
|---|---|---|---|
| API de email HTTP | fetch() a la API del proveedor | Una cuenta y una clave API en ese proveedor | El formato de petición de un solo proveedor y su precio por email |
| SMTP sobre TCP | nodemailer (con nodejs_compat) sobre los sockets TCP de Cloudflare | Credenciales SMTP de cualquier proveedor o buzón | Puertos permitidos, modo TLS y un viaje de ida y vuelta por cada comando SMTP |
| Cloudflare Email Service | El binding send_email | Un dominio remitente dado de alta en Email Sending | Enviar a cualquier destinatario requiere Workers Paid |
SMTP es la opción que te mantiene libre: el mismo código envía a través de Google Workspace, Zoho, Microsoft 365, Amazon SES, SendGrid, Mailgun, Postmark, Brevo o el SMTP de Resend. También es la que tiene trampas.
Qué puertos SMTP puede usar un Worker
Cloudflare bloquea las conexiones salientes por el puerto 25, así que un Worker no puede entregar directamente al servidor MX del destinatario. Tiene que pasar el mensaje a un servidor de envío, en uno de estos puertos:
| Puerto | Seguridad | Significado |
|---|---|---|
| 465 | TLS implícito (tls) | TLS desde el primer byte |
| 587 | STARTTLS (starttls) | Empieza en texto plano y pasa a TLS antes de autenticar |
| 2465 | TLS implícito (tls) | Alternativa al 465 que ofrecen algunos proveedores |
| 2525, 2587 | STARTTLS (starttls) | Alternativas al 587 que ofrecen algunos proveedores |
| 25 | Ninguna | Bloqueado en Workers |
Combina el puerto con el modo correcto. El 465 con STARTTLS, o el 587 con TLS implícito, falla durante el handshake TLS (wrong version number), antes de enviar ninguna credencial. MakiSend rechaza el puerto 25 al guardar una configuración y deduce el modo a partir del puerto.
nodemailer en Workers
nodemailer funciona en Workers en cuanto activas el flag de compatibilidad nodejs_compat: node:net y node:tls se apoyan en los sockets TCP de Cloudflare. Aun así, tres cosas nos dieron problemas.
- nodemailer resuelve el nombre de host y se conecta a la dirección IP. workerd rechaza una conexión a una dirección IP con TLS implícito en el 465 (
proxy request failed, cannot connect to the specified address), mientras que el mismo servidor abre sin problema por nombre. El error no dice nada de la causa real. dns.resolve4ydns.resolve6no están implementados en desarrollo local, así que un transporte por defecto puede quedarse colgado conwrangler dev.- Sin
family: 4, workerd registra avisos de red en cada envío.
La solución para los dos primeros es la opción getSocket de nodemailer: abre tú el socket, por nombre de host, y pásaselo. nodemailer sigue ejecutando STARTTLS cuando hace falta y sigue verificando el certificado contra el nombre de host.
La versión de MakiSend añade un tiempo límite y la comparten la comprobación de conexión, el envío de prueba y el envío real: smtp-connection.ts.
Cloudflare Email Service también habla SMTP en smtp.mx.cloudflare.net, pero un Worker no puede abrir un socket hacia ese host. Desde un Worker, envía con el binding send_email.
No envíes dentro de la petición
SMTP espera una respuesta a cada comando: TCP, TLS, EHLO, AUTH, MAIL FROM, RCPT TO, DATA, QUIT. En Workers un envío tarda de 300 a 400 ms en el mejor caso y unos 2 segundos en el peor, y un Worker abre una conexión nueva para cada email. Tu usuario no debería esperar eso, y un envío fallido no debería perderse.
- Responde
202 Accepteden cuanto el email esté guardado y después pon su id en una Cloudflare Queue. - Envía desde un Cloudflare Workflow: cada paso es durable, así que un reintento continúa donde se detuvo la ejecución.
- Reintenta solo lo que puede salir bien más tarde. MakiSend hace un intento más tres reintentos separados por 30 segundos; los errores de autenticación, TLS, DNS, configuración y 5xx se detienen de inmediato, y un email que agota sus intentos va a una cola de mensajes fallidos.
- Acepta un encabezado
Idempotency-Keypara que un cliente que repite su propia petición no envíe dos veces.
Esperar a la red no cuenta para el límite de CPU (10 ms por invocación en Workers Free), así que el envío cabe en el plan gratuito. La página de arquitectura sigue un email a través de estos pasos.
IP fijas y la bandeja de entrada
- Los Workers se conectan desde direcciones compartidas de Cloudflare que cambian. Un proveedor que solo acepta IP de una lista permitida rechazará la conexión: desactiva esa lista y confía en las credenciales SMTP, o envía desde un servidor con IP fija.
- Que el correo llegue a la bandeja de entrada depende del dominio remitente (SPF, DKIM y DMARC) y de la reputación del proveedor, no del Worker. En nuestras pruebas, los emails de MakiSend llegan a la bandeja de entrada una vez configurados esos tres registros.
- Gmail y Google Workspace limitan cuánto envía un buzón al día. Para volumen, usa un proveedor transaccional.
O deja que MakiSend lo haga
MakiSend es todo lo que hay en esta página, ya listo: un POST /send-email autenticado, una cola, un Workflow durable con reintentos, nodemailer sobre sockets abiertos por nombre de host, el binding de Cloudflare Email Service y un dashboard con los logs. Es open source y se despliega en tu propia cuenta de Cloudflare, incluida una de Workers Free.
- Con un agente de código: copia el prompt de instalación y pégalo en Claude Code, Codex, Cursor u otro agente.
- Sin agente: Deploy to Cloudflare, después de generar los dos secretos que pide.
- Lee primero el código: github.com/lixalistudio-oss/makisend.
Preguntas
¿Puede un Worker de Cloudflare enviar email por SMTP?
Sí. Con el flag nodejs_compat, nodemailer funciona sobre los sockets TCP de Cloudflare y habla con cualquier proveedor SMTP en los puertos 465, 587, 2465, 2525 o 2587. El puerto 25 está bloqueado.
¿Por qué no funciona el puerto 25 desde un Worker?
Cloudflare bloquea las conexiones salientes por el puerto 25. Usa el puerto de envío de tu proveedor: 465 con TLS implícito o 587 con STARTTLS.
¿Por qué nodemailer falla con "proxy request failed" en el puerto 465?
nodemailer se conecta a la dirección IP resuelta, y workerd rechaza TLS implícito hacia una IP sin nombre. Abre el socket por nombre de host con la opción getSocket de nodemailer.
¿Puedo usar Cloudflare Email Service en el plan gratuito?
No para enviar a cualquier destinatario: eso requiere Workers Paid ($5 al mes). En Workers Free, envía a través de otro proveedor SMTP.