rubis/apps/api/commands/send_test_email.ts
ordinarthur 1c5a58e09a
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 26s
Build & Deploy Landing / build-and-deploy (push) Successful in 27s
Build & Deploy API / build-and-deploy (push) Successful in 1m18s
chore(domain): migrate rubis.arthurbarre.fr → rubis.pro
Bascule du domaine principal vers rubis.pro / app.rubis.pro :

- K3s ConfigMaps (api.yml, web.yml) : APP_URL, WEB_URL,
  COOKIE_DOMAIN, OAUTH callbacks pointent vers app.rubis.pro
- Dockerfile.web : ARG VITE_API_URL et VITE_PUBLIC_LANDING_URL
- Workflows Gitea : commentaires + build args web → rubis.pro
- Code API (mail_dispatcher, send_test_email, config/mail) :
  defaults env LANDING_URL et MAIL_FROM_ADDRESS migrés
- Templates env (.env.example) idem
- Docs (architecture, backend, frontend, brand-identity) idem
- AGENTS.md / CLAUDE.md / deploy-memory : pointeurs domaine MAJ

Note : MAIL_FROM_ADDRESS dans le secret K3s reste sur
rubis@arthurbarre.fr tant que le domaine rubis.pro n'est pas
Verified dans Resend. À switcher manuellement après vérif Resend.

Compat : un 301 Traefik redirige rubis.arthurbarre.fr → rubis.pro
(et app.X aussi) — config Ansible dans le repo proxmox.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 21:32:31 +02:00

124 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BaseCommand, args, flags } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
import mail from '@adonisjs/mail/services/main'
import { render } from '@react-email/components'
import { DateTime } from 'luxon'
import env from '#start/env'
import { CheckinEmail } from '#mails/checkin_email'
import { RelanceEmail } from '#mails/relance_email'
/**
* Envoie un email de test via le mailer courant (typiquement Resend)
* pour valider la conf SPF/DKIM/clé API ET le rendu visuel des templates
* HTML React Email — sans passer par toute la chaîne facture → BullMQ.
*
* Usage :
* node ace send:test-email arthur@example.com # checkin
* node ace send:test-email arthur@example.com --template=checkin
* node ace send:test-email arthur@example.com --template=relance
* node ace send:test-email arthur@example.com --template=plain # ancien
* node ace send:test-email arthur@example.com --reply-to=patron@tpe.fr
*
* Default `checkin` car c'est le template avec les boutons CTA — le plus
* intéressant à valider visuellement au premier coup d'œil.
*/
export default class SendTestEmail extends BaseCommand {
static commandName = 'send:test-email'
static description = 'Envoie un email de test (HTML React Email) pour valider rendu + driver'
static options: CommandOptions = {
startApp: true,
}
@args.string({ description: 'Adresse destinataire' })
declare to: string
@flags.string({
description: 'Template à envoyer : checkin (default) | relance | plain',
default: 'checkin',
})
declare template: 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.pro')
const fromName = env.get('MAIL_FROM_NAME', "Rubis sur l'ongle")
const landingUrl = env.get('LANDING_URL', 'https://rubis.pro')
this.logger.info(`Driver: ${driver}`)
this.logger.info(`From: ${fromName} <${fromAddress}>`)
this.logger.info(`To: ${this.to}`)
this.logger.info(`Template: ${this.template}`)
if (this.replyTo) this.logger.info(`ReplyTo: ${this.replyTo}`)
// Données factices pour le rendu — évite d'avoir à créer une vraie
// facture en DB juste pour tester l'email.
const fakeInvoice = {
numero: 'F2026-TEST',
amountFormatted: '1 234,56 €',
dueDateFormatted: DateTime.utc().toFormat('dd/LL/yyyy'),
}
let subject: string
let text: string
let html: string | undefined
if (this.template === 'plain') {
subject = "[Rubis] Test d'envoi (plain text)"
text =
`Bonjour,\n\n` +
`Ceci est un email de test envoyé depuis Rubis sur l'ongle.\n` +
`Driver : ${driver}\n` +
`Date : ${new Date().toISOString()}\n\n` +
`— L'équipe Rubis`
html = undefined
} else if (this.template === 'relance') {
subject = `Facture ${fakeInvoice.numero} échue (test)`
text =
`Bonjour,\n\nPetit rappel pour la facture ${fakeInvoice.numero}.\n\nCordialement,\nArthur Barré`
html = await render(
RelanceEmail({
brandName: 'Arthur Barré (test)',
invoice: { ...fakeInvoice, daysLate: 3 },
bodyText: text,
landingUrl,
})
)
} else {
// checkin par défaut
subject = `[Rubis] Test — Facture ${fakeInvoice.numero}, payée ?`
text =
`Test d'envoi du template check-in.\n\n` +
`Lien "payée" : https://example.com/paid\n` +
`Lien "pending" : https://example.com/pending\n\n` +
`— L'équipe Rubis`
html = await render(
CheckinEmail({
invoice: fakeInvoice,
client: { name: 'Boulangerie Test' },
user: { fullName: 'Arthur Barré' },
paidUrl: 'https://example.com/paid',
pendingUrl: 'https://example.com/pending',
landingUrl,
})
)
}
const mailer = mail.use(driver)
const response = await mailer.send((m) => {
m.from(fromAddress, fromName).to(this.to).subject(subject).text(text)
if (html) m.html(html)
if (this.replyTo) m.replyTo(this.replyTo)
})
this.logger.success('Email envoyé')
if (response?.messageId) {
this.logger.info(`messageId: ${response.messageId}`)
}
}
}