From a790455ae1879f6f688773e4fecbdb290307eac5 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Wed, 6 May 2026 22:22:33 +0200 Subject: [PATCH] feat(api): bascule des envois mail sur Resend (fin de Mailpit en dev) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MAIL_DRIVER=resend par défaut, from rubis@arthurbarre.fr (domaine vérifié) - replyTo posé sur user.email dans les relances : les réponses des clients reviennent au patron de la TPE, pas dans notre boîte transactionnelle - ajout d'une commande Ace `send:test-email` pour valider la conf (driver, from, SPF/DKIM/clé API) sans passer par tout le flow facture Co-Authored-By: Claude Opus 4.7 --- apps/api/.env.example | 9 ++-- apps/api/app/services/mail_dispatcher.ts | 6 +++ apps/api/commands/send_test_email.ts | 61 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 apps/api/commands/send_test_email.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 57ccd8d..6c53faa 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -49,14 +49,15 @@ S3_SECRET_KEY=rubis-dev-secret S3_FORCE_PATH_STYLE=true #-------------------------------------------------------------------- -# Mail (Mailhog en dev, Resend en prod) +# Mail (Resend par défaut, Mailpit en fallback dev via MAIL_DRIVER=smtp) #-------------------------------------------------------------------- -MAIL_FROM_ADDRESS=relances@rubis-sur-l-ongle.fr +MAIL_FROM_ADDRESS=rubis@arthurbarre.fr MAIL_FROM_NAME=Rubis Sur l'Ongle -MAIL_DRIVER=smtp +MAIL_DRIVER=resend +RESEND_API_KEY= +# Fallback Mailpit (si MAIL_DRIVER=smtp) SMTP_HOST=localhost SMTP_PORT=1025 -RESEND_API_KEY= #-------------------------------------------------------------------- # OCR (Mistral) diff --git a/apps/api/app/services/mail_dispatcher.ts b/apps/api/app/services/mail_dispatcher.ts index 9e38f2b..519a58a 100644 --- a/apps/api/app/services/mail_dispatcher.ts +++ b/apps/api/app/services/mail_dispatcher.ts @@ -43,6 +43,12 @@ export async function sendRelanceEmail({ invoice, client, step, user }: RelanceP // Texte brut pour V1 — on ajoutera un template HTML quand on aura // décidé d'un look graphique pour les relances. .text(body) + // Reply-To pointe sur l'utilisateur Rubis : si le client final répond + // à la relance, sa réponse arrive chez le patron de la TPE, pas dans + // notre boîte transactionnelle. + if (user?.email) { + m.replyTo(user.email, user.fullName ?? user.email) + } }) } diff --git a/apps/api/commands/send_test_email.ts b/apps/api/commands/send_test_email.ts new file mode 100644 index 0000000..74f68a7 --- /dev/null +++ b/apps/api/commands/send_test_email.ts @@ -0,0 +1,61 @@ +import { BaseCommand, args, flags } from '@adonisjs/core/ace' +import type { CommandOptions } from '@adonisjs/core/types/ace' +import mail from '@adonisjs/mail/services/main' +import env from '#start/env' + +/** + * Envoie un email de test via le mailer courant (typiquement Resend) + * pour valider la conf SPF/DKIM/clé API sans passer par toute la chaîne + * facture → job BullMQ. + * + * node ace send:test-email arthur@example.com + * node ace send:test-email arthur@example.com --reply-to=patron@tpe.fr + */ +export default class SendTestEmail extends BaseCommand { + static commandName = 'send:test-email' + static description = 'Envoie un email de test via le mailer configuré (Resend en prod)' + + static options: CommandOptions = { + startApp: true, + } + + @args.string({ description: 'Adresse destinataire' }) + declare to: string + + @flags.string({ description: 'Adresse de reply-to (optionnelle)' }) + declare replyTo?: string + + async run() { + const driver = env.get('MAIL_DRIVER', 'smtp') + const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis-sur-l-ongle.fr') + const fromName = env.get('MAIL_FROM_NAME', "Rubis Sur l'Ongle") + + this.logger.info(`Driver: ${driver}`) + this.logger.info(`From: ${fromName} <${fromAddress}>`) + this.logger.info(`To: ${this.to}`) + if (this.replyTo) this.logger.info(`ReplyTo: ${this.replyTo}`) + + const mailer = mail.use(driver) + const response = await mailer.send((m) => { + m.from(fromAddress, fromName) + .to(this.to) + .subject('[Rubis] Test d\'envoi via Resend') + .text( + `Bonjour,\n\n` + + `Ceci est un email de test envoyé depuis Rubis Sur l'Ongle.\n` + + `Si vous recevez ce message, la conf Resend (SPF/DKIM/API key) est OK.\n\n` + + `Driver utilisé : ${driver}\n` + + `Date : ${new Date().toISOString()}\n\n` + + `— L'équipe Rubis` + ) + if (this.replyTo) m.replyTo(this.replyTo) + }) + + this.logger.success('Email envoyé') + // Resend renvoie un messageId dans la réponse — utile pour retrouver + // le log dans le dashboard. + if (response?.messageId) { + this.logger.info(`messageId: ${response.messageId}`) + } + } +}