Quand l'utilisateur confirme « Oui, payé » via check-in (lien email ou modale in-app) ou marque une facture encaissée manuellement, on envoie automatiquement un email de remerciement chaleureux au client final. Subject + body éditables par plan (mêmes variables que les relances), avec fallback hardcodé si vide. Gardé par la transition `* → paid` pour idempotence ; envoi async via BullMQ avec retry exponentiel ; capture en mode démo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
49 lines
1.5 KiB
TypeScript
49 lines
1.5 KiB
TypeScript
import DemoCapturedEmail from '#models/demo_captured_email'
|
|
import * as clock from '#services/clock'
|
|
import Organization from '#models/organization'
|
|
|
|
/**
|
|
* Service de capture d'emails en mode démo.
|
|
*
|
|
* Tout le code "démo" vit dans `services/demo/*` — la prod ne référence
|
|
* qu'une seule fonction (`captureEmailIfDemo`) depuis `mail_dispatcher`,
|
|
* pour minimiser le couplage.
|
|
*
|
|
* Si l'org est en mode démo : crée une `DemoCapturedEmail` avec le
|
|
* timestamp virtualNow et retourne `true` → caller doit ne PAS envoyer
|
|
* via Resend.
|
|
*
|
|
* Sinon : retourne `false` → comportement prod inchangé.
|
|
*/
|
|
export type CaptureInput = {
|
|
organizationId: string
|
|
kind: 'relance' | 'checkin' | 'thanks'
|
|
to: { email: string; name?: string | null }
|
|
from: { email: string; name?: string | null }
|
|
replyTo?: string | null
|
|
subject: string
|
|
body: string
|
|
meta?: Record<string, unknown>
|
|
}
|
|
|
|
export async function captureEmailIfDemo(input: CaptureInput): Promise<boolean> {
|
|
const org = await Organization.find(input.organizationId)
|
|
if (!org || !org.demoMode) return false
|
|
|
|
const sentAt = await clock.now(input.organizationId)
|
|
await DemoCapturedEmail.create({
|
|
organizationId: input.organizationId,
|
|
kind: input.kind,
|
|
toEmail: input.to.email,
|
|
toName: input.to.name ?? null,
|
|
fromEmail: input.from.email,
|
|
fromName: input.from.name ?? null,
|
|
replyTo: input.replyTo ?? null,
|
|
subject: input.subject,
|
|
body: input.body,
|
|
meta: input.meta ?? {},
|
|
sentAt,
|
|
})
|
|
return true
|
|
}
|