Envoyer des e-mails depuis Cloudflare Workers
Un Worker ne peut pas faire tourner un serveur de messagerie, mais il peut parler à un serveur. Voici ce qui marche, ce qui échoue et pourquoi, appris en construisant MakiSend, une API d’e-mail qui tourne entièrement sur Workers.
Dernière mise à jour · Version Markdown (en anglais)
Trois façons d’envoyer
Un Worker a trois portes de sortie. Choisissez selon le fournisseur dont vous voulez dépendre, pas selon le code : il est court dans tous les cas.
| Approche | Comment le Worker envoie | Ce qu’il vous faut | Points d’attention |
|---|---|---|---|
| API d’e-mail HTTP | fetch() vers l’API du fournisseur | Un compte et une clé API chez ce fournisseur | Le format de requête d’un seul fournisseur, et son prix par e-mail |
| SMTP sur TCP | nodemailer (avec nodejs_compat) sur les sockets TCP de Cloudflare | Les identifiants SMTP de n’importe quel fournisseur ou boîte mail | Ports autorisés, mode TLS et un aller-retour par commande SMTP |
| Cloudflare Email Service | Le binding send_email | Un domaine d’envoi ajouté à Email Sending | Envoyer à n’importe quel destinataire demande Workers Paid |
Le SMTP est celui qui vous garde libre : le même code envoie via Google Workspace, Zoho, Microsoft 365, Amazon SES, SendGrid, Mailgun, Postmark, Brevo ou le SMTP de Resend. C’est aussi celui qui a des pièges.
Les ports SMTP utilisables par un Worker
Cloudflare bloque les connexions sortantes sur le port 25 : un Worker ne peut donc pas livrer directement au serveur MX du destinataire. Il doit confier le message à un serveur de soumission, sur l’un de ces ports :
| Port | Sécurité | Signification |
|---|---|---|
| 465 | TLS implicite (tls) | TLS dès le premier octet |
| 587 | STARTTLS (starttls) | Commence en clair, passe en TLS avant l’authentification |
| 2465 | TLS implicite (tls) | Alternative au 465 proposée par certains fournisseurs |
| 2525, 2587 | STARTTLS (starttls) | Alternatives au 587 proposées par certains fournisseurs |
| 25 | Aucune | Bloqué sur Workers |
Associez le port au bon mode. Le 465 avec STARTTLS, ou le 587 avec TLS implicite, échoue pendant la négociation TLS (wrong version number), avant l’envoi de tout identifiant. MakiSend refuse le port 25 à l’enregistrement d’une configuration et déduit le mode du port.
Faire tourner nodemailer sur Workers
nodemailer tourne sur Workers dès que l’option de compatibilité nodejs_compat est activée : node:net et node:tls reposent sur les sockets TCP de Cloudflare. Trois choses nous ont quand même piégés.
- nodemailer résout le nom d’hôte et se connecte à l’adresse IP. workerd refuse une connexion à une adresse IP en TLS implicite sur le 465 (
proxy request failed, cannot connect to the specified address), alors que le même serveur s’ouvre sans problème par son nom. L’erreur ne dit rien de la vraie cause. dns.resolve4etdns.resolve6ne sont pas implémentés en développement local : un transport par défaut peut donc rester bloqué souswrangler dev.- Sans
family: 4, workerd écrit des avertissements réseau à chaque envoi.
Pour les deux premiers, la solution est l’option getSocket de nodemailer : ouvrez le socket vous-même, par nom d’hôte, et transmettez-le. nodemailer lance toujours STARTTLS quand il le faut et vérifie toujours le certificat par rapport au nom d’hôte.
La version de MakiSend ajoute un délai maximal et sert à la fois au test de connexion, à l’envoi de test et à l’envoi réel : smtp-connection.ts.
Cloudflare Email Service parle aussi SMTP sur smtp.mx.cloudflare.net, mais un Worker ne peut pas ouvrir de socket vers cet hôte. Depuis un Worker, passez plutôt par le binding send_email.
N’envoyez pas pendant la requête
SMTP attend une réponse à chaque commande : TCP, TLS, EHLO, AUTH, MAIL FROM, RCPT TO, DATA, QUIT. Sur Workers, un envoi prend 300 à 400 ms dans le meilleur des cas et environ 2 secondes dans le pire, et un Worker ouvre une nouvelle connexion pour chaque e-mail. Votre utilisateur ne devrait pas attendre cela, et un envoi raté ne devrait pas être perdu.
- Répondez
202 Accepteddès que l’e-mail est enregistré, puis placez son id dans une Cloudflare Queue. - Envoyez depuis un Cloudflare Workflow : chaque étape est durable, donc une relance reprend là où l’exécution s’est arrêtée.
- Ne relancez que ce qui peut réussir plus tard. MakiSend fait une tentative plus trois relances à 30 secondes d’intervalle ; les erreurs d’authentification, de TLS, de DNS, de configuration et les 5xx arrêtent tout de suite, et un e-mail qui a épuisé ses tentatives part dans une file de lettres mortes.
- Acceptez un en-tête
Idempotency-Keypour qu’un client qui relance sa propre requête n’envoie pas deux fois.
L’attente réseau ne compte pas dans la limite de CPU (10 ms par invocation sur Workers Free) : l’envoi lui-même tient donc dans le plan gratuit. La page architecture suit un e-mail à travers ces étapes.
IP fixes et boîte de réception
- Les Workers se connectent depuis des adresses Cloudflare partagées qui changent. Un fournisseur qui n’accepte que des IP sur liste blanche refusera la connexion : désactivez la liste blanche et comptez sur les identifiants SMTP, ou envoyez depuis un serveur à IP fixe.
- L’arrivée en boîte de réception dépend du domaine d’envoi (SPF, DKIM et DMARC) et de la réputation du fournisseur, pas du Worker. Dans nos tests, les e-mails de MakiSend arrivent en boîte de réception une fois ces trois enregistrements en place.
- Gmail et Google Workspace plafonnent le nombre d’envois quotidiens d’une boîte. Pour du volume, utilisez un fournisseur transactionnel.
Ou laissez MakiSend s’en charger
MakiSend, c’est tout ce qui est décrit sur cette page, prêt à l’emploi : un POST /send-email authentifié, une file d’attente, un Workflow durable avec relances, nodemailer sur des sockets ouverts par nom d’hôte, le binding Cloudflare Email Service et un dashboard avec les logs. Il est open source et se déploie sur votre propre compte Cloudflare, y compris un compte Workers Free.
- Avec un agent de code : copiez le prompt d’installation et collez-le dans Claude Code, Codex, Cursor ou un autre agent.
- Sans agent : Deploy to Cloudflare, après avoir généré les deux secrets demandés.
- Lire le code d'abord : github.com/lixalistudio-oss/makisend.
Questions
Un Worker Cloudflare peut-il envoyer des e-mails en SMTP ?
Oui. Avec l’option nodejs_compat, nodemailer tourne sur les sockets TCP de Cloudflare et parle à n’importe quel fournisseur SMTP sur les ports 465, 587, 2465, 2525 ou 2587. Le port 25 est bloqué.
Pourquoi le port 25 ne fonctionne-t-il pas depuis un Worker ?
Cloudflare bloque les connexions sortantes sur le port 25. Utilisez plutôt le port de soumission de votre fournisseur : 465 en TLS implicite ou 587 en STARTTLS.
Pourquoi nodemailer échoue-t-il avec « proxy request failed » sur le port 465 ?
nodemailer se connecte à l’adresse IP résolue, et workerd refuse le TLS implicite vers une IP nue. Ouvrez le socket par nom d’hôte avec l’option getSocket de nodemailer.
Puis-je utiliser Cloudflare Email Service avec le plan gratuit ?
Pas pour envoyer à n’importe quel destinataire : il faut Workers Paid ($5 par mois). Sur Workers Free, envoyez via un autre fournisseur SMTP.