
Microservices e-mail Cloud : guide d'utilisation d'AWS Lambda et Cloudflare Workers
Déployez un microservice e-mail sur Lambda et gérez les files de messages, invocable depuis Cloudflare Workers ou n'importe quel backend Node.js.
Résumer avec l'IA :
Tu as développé ta page de contact et tu veux envoyer un e-mail à la soumission du formulaire ?
Dans cet article, on va voir comment envoyer des e-mails gratuitement avec :
Une API e-mail tierce, avec un comparatif des solutions existantes.
AWS Lambda pour héberger le microservice e-mail.
Le backend de ton app en Node.js ou avec le méta-framework remix.run, hébergé sur Cloudflare Workers dans une architecture en microservices.
Tu vas apprendre à :
Créer un microservice e-mail avec AWS Lambda et la file AWS SQS.
Créer un template CloudFormation et un template SAM.
Déployer sur AWS de façon reproductible.
Publier un événement vers AWS avec
@aws-sdk/client-sqsdepuis ton app Node.js/Remix.Créer un microservice dans Cloudflare avec Cloudflare Workers et wrangler.
Ce worker Cloudflare centralise la logique de publication d’événements, pour que les autres workers se contentent de l’appeler pour déclencher notre logique d’envoi d’e-mails AWS.
Tu peux retrouver tout le code sur mon repo GitHub.
Pour des raisons de compatibilité, j’utilise des bibliothèques qui fonctionnent sur Cloudflare Workers.
Les workers Cloudflare ne tournent pas sur Node.js mais sur des isolates V8, le même moteur de navigateur que celui de Chrome et Chromium.
Créer un FaaS e-mail avec AWS Lambda
On peut partir sur n’importe quelle solution d’envoi :
| Service d’API e-mail | Offre gratuite / essai | Limite du plan gratuit | Notes sur le plan gratuit | Tarif après l’offre gratuite (exemple) | Débit maximal du plan gratuit |
|---|---|---|---|---|---|
| Resend | Oui | 3 000 e-mails/mois, 100 e-mails/jour | Plan gratuit généreux : 1 domaine personnalisé, rétention des données 1 jour, support par ticket. Le surplus est bloqué une fois le quota mensuel atteint. | Pro : 20 $/mois pour 50 000 e-mails ; Scale : 90 $/mois pour 100 000 e-mails ; option IP dédiée disponible | 2 e-mails par seconde |
| SendGrid | Oui | 100 e-mails/jour pendant 60 jours | Bon pour les petits volumes et les tests | Formules payantes à partir de 19,95 $/mois pour 50 000 e-mails | ~600 requêtes API/min sur la plupart des endpoints |
| Mailgun | Oui | 100 e-mails/jour | Essai le premier mois | À partir de 35 $/mois pour plus de 10 000 e-mails | ~300 messages/min |
| Postmark | Pas de plan gratuit, paiement à l’usage | Pas de plan gratuit | Axé sur la délivrabilité | 15 $ pour 10 000 e-mails | |
| Amazon SES | Oui pendant les 12 premiers mois | 3 000 e-mails/mois | Intégration à l’écosystème AWS | 0,10 $ pour 1 000 e-mails après l’offre gratuite | 14 e-mails/s, avec des rafales jusqu’à 28 e-mails/s pour les nouveaux comptes, ajustable sur demande |
| MailerSend | Oui | 12 000 e-mails/mois | Plan gratuit généreux pour les petites équipes. Le surplus coûte 1,00 $ / 1 000 e-mails | Formules à partir de 15 $/mois | |
| Brevo (Sendinblue) | Oui | 300 e-mails/jour | Limite d’envoi quotidienne | Formules payantes à partir de 15 $/mois | |
| Elastic Email | Oui | 100 e-mails/jour | Petit plan gratuit quotidien | Formules payantes à partir d’environ 9 $/mois | |
| Mailtrap | Oui, surtout pour les tests | 3 500 e-mails/mois | Pensé pour la préproduction et les tests | Formules payantes à partir de 10 $/mois |
Côté tarifs, Brevo, Mailtrap, Amazon SES et Resend semblent de bons candidats.
Je pars sur Resend pour son tableau de bord, qui permet d’exporter en CSV.
Toutes les API e-mail imposent un débit maximal. Avec Resend, au-delà de 2 e-mails par seconde tu reçois une erreur de rate limit (429). Plutôt qu’un appel direct à l’API e-mail, il vaut donc mieux passer par une file.
Consommer les e-mails (Pub/Sub)
Dans un système de file, on a des publishers et des subscribers. Le publisher crée la tâche à traiter. Le subscriber écoute la file. Quand une tâche arrive, on dit qu’elle est consommée.
Dans notre cas, quand un e-mail doit partir, on crée une tâche dans la file. Le handler de la tâche est abonné à la file et envoie l’e-mail via l’API Resend.
Profiter des files des plateformes cloud
Pour rester simple, on utilise le FaaS (Functions as a Service) des plateformes cloud avec la file qu’elles proposent : ça ne coûte rien et consomme moins de ressources.
On peut monter notre microservice e-mail avec :
Cloudflare, avec un worker Cloudflare et des queues Cloudflare.
AWS : une fonction Lambda avec Amazon Simple Queue Service (SQS).
Les queues Cloudflare coûtent 5 $/mois, hors budget pour ce challenge.
→ On part sur AWS.
Implémenter le microservice e-mail avec AWS Lambda
L’e-mail à envoyer doit être validé. On utilise yup (on verra pourquoi plus loin).
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950export const schema = yup .object({ to: recipientsSchema, // single or multiple recipients from: senderSchema, subject: yup.string().required(), html: yup.string(), text: yup.string(), }) .test( "html-or-text-required", "Either html or text must be provided", function (value) { return !!(value?.html || value?.text); }, );// single recipientconst recipientSchema = yup.object({ name: yup.string().nullable().optional(), email: yup.string().email().required(),});// multiple recipientsconst recipientsSchema = yup.lazy((value) => { if (Array.isArray(value)) { return yup.array().of(recipientSchema).required(); } return recipientSchema.required();});const senderSchema = yup .object({ name: yup.string().nullable().optional(), email: yup.string().email().required(), }) // .transform((value, originalValue) => { if ( originalValue === undefined || originalValue === null || (typeof originalValue === "object" && originalValue !== null && Object.keys(originalValue).length === 0) ) { return undefined; } return value; }) .optional();L’entrée accepte soit un destinataire, soit un tableau de destinataires.
Chaque destinataire a un nom optionnel et un e-mail obligatoire.
L’expéditeur est optionnel, tout comme son nom.
Envoyer un e-mail avec Resend
Pour appeler l’API Resend, on utilise leur bibliothèque npm resend, en respectant ces conditions :
En cas d’erreur de rate limit, réessayer après la durée de throttling.
Les champs to et from doivent prendre la forme
${recipient.name} <${recipient.email}>, ou tout simplementrecipient.name.
12345678910111213141516171819202122232425262728293031323334353637383940414243import { Schema } from "../schema";import { CreateEmailOptions, Resend } from "resend";import { getRecipientsField, getSenderField } from "../helpers/email";// Resend has a limit of 2 emails per secondconst retryAfter = 500;export async function sendEmail(body: Schema, resend?: Resend) { if (!resend) { throw new Error("Resend instance is required"); } const payload: CreateEmailOptions = { from: getSenderField(body.from), to: getRecipientsField(body.to), subject: body.subject, html: body.html, text: body.text, react: undefined, }; const { data, error } = await resend.emails.send(payload); if (error) { if (isResendRateLimitError(error)) { await sleep(retryAfter); return sendEmail(body, resend); } // Throw to trigger Lambda retry and DLQ if needed throw new Error(JSON.stringify({ ...error, payload })); }}function isResendRateLimitError(error: unknown): boolean { if (typeof error !== "object" || error === null) return false; // Resend SDK returns the error as a plain Error with a message string const message = (error as Error).message; return message?.includes("rate_limit_exceeded");}async function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms));}123456789101112131415161718192021222324252627import { RecipientSchema, RecipientsSchema, SenderSchema } from "../schema";export const getSenderField = (sender?: SenderSchema) => { if (!sender) { return `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`; } if (sender.name?.trim()) { return `${sender.name} <${sender.email}>`; } return sender.email;};export const getRecipientsField = ( recipient: RecipientsSchema,): string | string[] => { if (Array.isArray(recipient)) { return recipient.map((r) => getRecipientField(r)); } return getRecipientField(recipient);};const getRecipientField = (recipient: RecipientSchema) => { if (recipient.name?.trim()) { return `${recipient.name} <${recipient.email}>`; } return recipient.email;};S’authentifier auprès d’une API tierce depuis AWS Lambda
L’appel à l’API e-mail de Resend doit être authentifié. Sa clé API doit être stockée de façon sécurisée. Pour ça, on utilise AWS Secrets Manager (SSM).
Créer le secret dans SSM.
Habituellement, pour récupérer le secret, on lie des variables par leur clé dans la configuration de la fonction.
Mais ça le rend visible dans la configuration d’environnement de la fonction.
Alternative : récupérer le secret SSM à l’exécution avec@aws-sdk/client-secrets-manager.
1234567891011121314151617import { SecretsManagerClient, GetSecretValueCommand,} from "@aws-sdk/client-secrets-manager";async function getResendApiKey(): Promise<string> { const client = new SecretsManagerClient({}); const command = new GetSecretValueCommand({ SecretId: process.env.RESEND_SECRET_NAME, }); const response = await client.send(command); if (!response.SecretString) { throw new Error("Resend API key not found"); } return response.SecretString;}Voyons maintenant la logique principale : comment notre fonction Lambda gère-t-elle les messages Amazon SQS ?
Implémenter un handler de messages AWS SQS
Pour gérer les messages des files SQS, on exporte un handler de type SQSHandler :
12345678910111213141516171819202122232425262728293031323334353637383940const allowedSenderEmails = (process.env.ALLOWED_SENDERS || "contact@example.com").split(",");export const handler: SQSHandler = async (event: SQSEvent) => { const resend = new Resend(await getResendApiKey()); const failedRecords: SQSBatchResponse["batchItemFailures"] = []; for (const record of event.Records) { try { if (!record.body) { throw new Error("Invalid input"); } const body = JSON.parse(record.body); await validateBodyAsync(body); // Validate sender email if provided if (body.from_email && !allowedSenderEmails.includes(body.from_email)) { throw new Error( `Sender email ${body.from_email} is not allowed. Allowed emails: ${allowedSenderEmails.join(", ")}`, ); } await sendResendEmail(body, resend); } catch (err) { console.error(err); failedRecords.push({ itemIdentifier: record.messageId, }); } } if (failedRecords.length > 0) { return { batchItemFailures: failedRecords, }; }};La valeur de retour batchItemFailures indique à Lambda quels messages précis du lot ont échoué, pour qu’AWS ne remette en file que ceux-là au lieu de rejouer tout le lot.
Sans batchItemFailures, un seul message en échec fait échouer tout le lot.
Ça peut entraîner :
Un traitement en double des messages déjà passés.
Des temps de traitement plus longs.
Des coûts plus élevés (si tu as pris un plan e-mail premium).
Configurer et invoquer la fonction Lambda avec SAM
La fonction Lambda peut s’invoquer avec Serverless Framework ou AWS SAM (Serverless Application Model). La principale différence : Serverless Framework est multi-cloud, alors que SAM ne gère que AWS (mais de façon profonde). Par simplicité, on prend AWS SAM : tout est intégré.
1234567891011121314151617181920212223242526272829303132333435# More information about the configuration file can be found here:# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.htmlversion = 0.1[default.global.parameters]stack_name = "prod-notification-service-sh"[default.build.parameters]cached = trueparallel = true[default.validate.parameters]lint = true[default.deploy.parameters]capabilities = "CAPABILITY_IAM"confirm_changeset = trueresolve_s3 = trues3_prefix = "prod-notification-service-sh"region = "eu-west-3"[default.package.parameters]resolve_s3 = trues3_prefix = "prod-notification-service-sh"output_template_file = "packaged.yaml"[default.sync.parameters]watch = truestack_name = "prod-notification-service-sh"[default.local_start_api.parameters]warm_containers = "EAGER"[default.local_start_lambda.parameters]warm_containers = "EAGER"Pour invoquer la fonction, il faut simuler un événement SQS, ce qu’on fera dans un cas de test jest.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import type { SQSEvent, SQSMessageAttributes, SQSRecordAttributes,} from "aws-lambda";import { handler } from "../src";import type { Schema } from "../src/schema";import { v4 as uuidv4 } from "uuid";describe("Unit test for app handler", function () { it("sends text email", async () => { const emails: Schema[] = [ { to: { email: "YOUR_EMAIL@gmail.com" }, subject: "Test Email", text: "This is a test email.", }, ]; const events = getSQSEvents(emails); const result = await handler(events, {} as any, () => {}); // Add assertions here to verify the email was sent correctly });});const getSQSEvents = (emails: Schema[]): SQSEvent => { const attributes: SQSRecordAttributes = { ApproximateReceiveCount: "0", SentTimestamp: "1630456800000", SenderId: "", ApproximateFirstReceiveTimestamp: "1630456800000", }; const messageAttributes: SQSMessageAttributes = { dummy: { dataType: "String", stringValue: "dummy", }, }; const event: SQSEvent = { Records: emails.map((email) => ({ messageId: uuidv4(), receiptHandle: "", attributes, messageAttributes, md5OfBody: "", md5OfMessageAttributes: "", eventSource: "", eventSourceARN: "", awsRegion: "eu-west-3", body: JSON.stringify(email), })), }; return event;};Pour lancer le test, ajoute "test": "jest" à ton package.json, puis lance pnpm run test.
Déployer ta fonction Lambda
Toujours pour rester simple, on utilise CloudFormation pour déployer la fonction Lambda et les files.
Pas de Terraform ici : le code de la fonction Lambda finirait dans le state TF.
Le déploiement suit cette architecture :

Frontend : soumet le formulaire de contact et envoie une requête POST anonyme au backend.
Backend : reçoit l’e-mail à envoyer et publie une tâche dans la file SQS (qu’il tourne sur Node.js ou sur un isolate V8).
Amazon SQS : contient les e-mails à envoyer.
AWS Lambda : s’abonne à la file (3) et traite les e-mails en les envoyant via une API e-mail prise en charge (6) ou (7).
Bucket S3 : héberge le code de la fonction Lambda. Il est créé implicitement par CloudFormation.
Dead Letter Queue : contient les messages qui n’ont pas pu être traités après 3 tentatives.
Resend : API e-mail (nécessite de stocker un secret dans SSM).
AMAZON SES : API e-mail (aucun secret requis, intégrée à l’écosystème AWS).
Montons le template.yaml CloudFormation :
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293AWSTemplateFormatVersion: "2010-09-09"Transform: AWS::Serverless-2016-10-31Description: > A serverless application that processes email notifications from an SQS queue.Globals: Function: Timeout: 30 MemorySize: 256 Tracing: Active LoggingConfig: LogFormat: JSON Environment: Variables: EMAIL_SERVICE: "resend" FROM_NAME: "Your Firm" FROM_EMAIL: "contact@example.com" ALLOWED_SENDERS_EMAILS: "contact@example.com" RESEND_SECRET_NAME: "prod-notification-service-resend-api-key"Resources: NotificationServiceLambda: Type: AWS::Serverless::Function Properties: FunctionName: prod-notification-service-sh-lambda CodeUri: ./ Handler: dist/src/index.handler Runtime: nodejs20.x Architectures: - x86_64 Events: SQSEvent: Type: SQS Properties: Queue: !GetAtt SQSQueue.Arn BatchSize: 2 Enabled: true Policies: - SQSPollerPolicy: QueueName: !GetAtt SQSQueue.QueueName - Statement: - Effect: Allow Action: - ses:SendEmail - ses:SendRawEmail Resource: "*" - Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" - Statement: - Effect: Allow Action: secretsmanager:GetSecretValue Resource: arn:aws:secretsmanager:YOUR_REGION:YOUR_ACCOUNT_ID:secret:prod-notification-service-* SQSQueue: Type: AWS::SQS::Queue Properties: QueueName: prod-notification-service-sh-queue VisibilityTimeout: 120 RedrivePolicy: deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn maxReceiveCount: 3 DeadLetterQueue: Type: AWS::SQS::Queue Properties: QueueName: prod-notification-service-sh-deadletter-queue MessageRetentionPeriod: 1209600 # 14 days in secondsOutputs: NotificationServiceLambda: Description: Notification Service Lambda Function ARN Value: !GetAtt NotificationServiceLambda.Arn SQSQueueURL: Description: URL of the SQS Queue Value: !Ref SQSQueue SQSQueueARN: Description: ARN of the SQS Queue Value: !GetAtt SQSQueue.Arn DeadLetterQueueURL: Description: URL of the Dead Letter Queue Value: !Ref DeadLetterQueue DeadLetterQueueARN: Description: ARN of the Dead Letter Queue Value: !GetAtt DeadLetterQueue.ArnDéclare ces scripts dans ton package.json :
1234567"scripts": { "build": "rm -rf dist && tsc && sam build", "deploy": "pnpm run build && sam deploy --disable-rollback", "delete": "aws cloudformation delete-stack --stack-name prod-notification-service-sh --region eu-west-3", "log": "sam logs -n NotificationServiceLambda --stack-name prod-notification-service-sh --tail", "test": "jest"},Pour déployer d’un coup le template CloudFormation et le code Lambda :
Créer le secret directement dans le tableau de bord Amazon SSM.
Note : dans le repo GitHub, le secret SSM est créé avec Terraform.
Lancer
pnpm run deploy.
La commande transpile le TypeScript en JavaScript, empaquette le code avecsam buildet déploie avecsam deploy.
Ça crée la stack CloudFormation prod-notification-service-sh dans ta région configurée. Tu peux suivre ses ressources dans le service AWS CloudFormation de la console AWS.
Pour tout remettre à zéro, lance : aws cloudformation delete-stack --stack-name prod-notification-service-sh --region YOUR_REGION
Et voilà, tout tourne !
Publier un événement vers AWS SQS
Si on arrive à publier un événement dans la file qu’écoute notre fonction Lambda, l’e-mail part.
On peut publier un événement directement avec @aws-sdk/client-sqs. Mais si 10 applications appellent ta fonction Lambda, ça fait beaucoup de code dupliqué.
Si ton app tourne sur remix.run et Cloudflare Workers, je te recommande de créer un worker Cloudflare dont l’unique rôle est de publier les tâches d’e-mail vers Amazon SQS :

Utilisateur : soumet le formulaire de contact à ton app.
Worker de l’app : ton app fait un simple appel HTTP à ton mail-worker.
Worker du microservice e-mail : ton mail-worker gère la publication des événements SQS.
Grafana Loki : surveille les éventuelles erreurs, en les publiant vers Loki.
File SQS : met en file les événements e-mail.
Pour publier les éventuelles erreurs vers Loki et recevoir des alertes par e-mail :
Créer un compte Grafana Cloud : un compte sur Grafana Cloud donne accès à Loki, un système d’agrégation de logs.
Configurer Loki : intégrer Loki à ton application pour collecter les logs. Via l’API Loki ou une bibliothèque de logging compatible, tes logs partent vers Loki.
Créer des alertes dans Grafana : définir des règles d’alerte basées sur les logs collectés par Loki. Tu peux déclencher une alerte sur un message d’erreur précis ou sur un motif de log.
Configurer les notifications e-mail : brancher un canal de notification e-mail dans Grafana et le relier à tes règles d’alerte, pour recevoir un e-mail quand une alerte se déclenche.
Si tu es sur le plan Cloudflare starter, préfère Cloudflare Worker Tailing pour suivre les logs de ton worker.
Loki fonctionne en push (le worker pousse les logs vers Loki avec workers-loki-logger), alors que le tailing fonctionne en pull. Même si le worker Cloudflare plante au démarrage, les logs suivis sont quand même récupérés, ce qui n’est pas le cas avec une méthode en push.
Implémentons le worker-email-microservice
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495import { Logger } from "workers-loki-logger";import { SQSClient, SendMessageCommand,} from "@aws-sdk/client-sqs";type Bindings = { AWS_SQS_ACCESS_KEY_ID: string; AWS_SQS_SECRET_ACCESS_KEY: string; AWS_SQS_REGION: string; AWS_SQS_QUEUE_URL: string; LOKI_SECRET: string; LOKI_URL: string; ENVIRONMENT: string;};function getLogger(context: ExecutionContext, env: Bindings) { return new Logger({ cloudflareContext: context, lokiSecret: env.LOKI_SECRET, lokiUrl: env.LOKI_URL || "https://logs-prod-eu-west-0.grafana.net", stream: { worker: "newsletter-worker", environment: env.ENVIRONMENT, }, });}async function handleSendEmail( req: Request, env: Bindings, ctx: ExecutionContext, logger: Logger,): Promise<Response> { const sqsClient = new SQSClient({ region: env.AWS_SQS_REGION, credentials: { accessKeyId: env.AWS_SQS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY, }, }); const parsedBody = await req.json(); const command = new SendMessageCommand({ QueueUrl: env.AWS_SQS_QUEUE_URL, MessageBody: JSON.stringify(parsedBody), }); console.log(`Sending email to SQS: ${JSON.stringify(command)}`); const result = await sqsClient.send(command); return result.$metadata.httpStatusCode === 200 ? new Response("Email queued in SQS") : Response.json({ status: 500, message: "Failed to queue email in SQS", requestId: result.$metadata.requestId, });}export default { async fetch( req: Request, env: Bindings, ctx: ExecutionContext, ): Promise<Response> { const logger = getLogger(ctx, env); logger.mdcSet("requestUrl", req.url); try { const url = new URL(req.url); // Send email directly on POST request if (req.method === "POST" && url.pathname === "/send-email") { return handleSendEmail(req, env, ctx, logger); } logger.error( "Route not found", new Error(`Route ${req.method} ${url.pathname} not found`), ); return new Response("Not found", { status: 404 }); } catch (error) { logger.error("Caught error", error); return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "Content-Type": "application/json" }, }); } finally { await logger.flush(); } }}Déclare les secrets nécessaires dans .dev.vars pour le développement local :
12345AWS_SQS_ACCESS_KEY_ID=AWS_SQS_SECRET_ACCESS_KEY=AWS_SQS_REGION=eu-west-3AWS_SQS_QUEUE_URL=LOKI_SECRET=Déclare LOKI_URL dans wrangler.toml.
Pour déployer le worker en production :
123456789pnpm install# Define all secrets on remote worker.# On the first time, it will also create the worker.npx wrangler secret put -e production AWS_SQS_ACCESS_KEY_IDnpx wrangler secret put -e production AWS_SQS_SECRET_ACCESS_KEYnpx wrangler secret put -e production AWS_SQS_REGIONnpx wrangler secret put -e production AWS_SQS_QUEUE_URLnpx wrangler secret put -e production LOKI_SECRETpnpm run deployLe déploiement en production se fait avec pnpm run deploy.
Appeler ton worker-email-microservice
Tout autre worker peut maintenant appeler le binding de worker-email-microservice en HTTP pour envoyer un e-mail.
Ajouter le binding à tes apps workers (PAS AU MICROSERVICE)
Disons que ta page de contact tourne sur le worker
landing-page-worker. Ajoute dans sa configurationwrangler.jsoncun bindingNOTIFICATIONpointant versworker-email-microservice.
Note : si tu utilises des environnements, déclare le binding propre à l’environnement en plus des bindings de niveau supérieur
1234567891011121314151617181920{ "services": [ { "binding": "NOTIFICATION", "service": "notification", "environment": "production" } ], "env": { "production": { "services": [ { "binding": "NOTIFICATION", "service": "notification", "environment": "production" } ] } }}- Envoyer un e-mail depuis
landing-page-workeravec une simple requête HTTP au bindingNOTIFICATION.
12345678910111213141516171819202122232425262728const res = await context.cloudflare.env.NOTIFICATION.fetch( new Request(`https://dummy/send-email`, { method: "POST", body: JSON.stringify({ to: { name: `${firstName} ${lastName}`, email: email, }, subject: `${subject}`, txt, html, }), headers: { "Content-Type": "application/json" }, }) ); if (!res.ok) { const errorData = await res.json(); return Response.json( { success: false, error: errorData.message || "Failed to send email", }, { status: res.status, } ); }Content, tout tourne sans coût supplémentaire :’)
Je me demande juste pourquoi je tape le quota d’e-mails tout le temps, je suis pas si connu que ça 🫠
Voyons le tableau de bord une seconde…

Louche, non ?

J’ai écrit tout un article pour protéger tes formulaires du spam (avec le code qui va avec), je te recommande vraiment de le parcourir pour éviter ce genre de surprise : https://antoninmarxer.hashnode.dev/7-ways-to-stop-form-spam-in-remix-nodejs
En résumé
Au final, ça coûte 1 $/mois : c’est la facture AWS SSM pour stocker une seule clé.
Disons que ça valide le challenge du service d’envoi quasi gratuit.
Tu sais maintenant :
Créer un microservice e-mail avec AWS Lambda et la file AWS SQS.
Créer un template CloudFormation et un template SAM.
Déployer sur AWS de façon reproductible.
Publier un événement vers AWS avec
@aws-sdk/client-sqs.Créer un microservice dans Cloudflare avec Cloudflare Workers et wrangler.
Dis-moi ce que tu penses de cette stack !
Comment tu envoies les e-mails dans tes apps, et pourquoi avoir choisi cette solution ?
Table of Contents
- Créer un FaaS e-mail avec AWS Lambda
- Implémenter le microservice e-mail avec AWS Lambda
- Envoyer un e-mail avec Resend
- S’authentifier auprès d’une API tierce depuis AWS Lambda
- Implémenter un handler de messages AWS SQS
- Configurer et invoquer la fonction Lambda avec SAM
- Déployer ta fonction Lambda
- Publier un événement vers AWS SQS
- Implémentons le worker-email-microservice
- Appeler ton worker-email-microservice
- En résumé
Comments (0)
No comments yet. Be the first!