From ff8fe64be2af7c6ab575047545a6baa138ba8ea4 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Thu, 7 May 2026 18:10:27 +0200 Subject: [PATCH] feat(mail): templates HTML React Email + brand "Rubis sur l'ongle" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Templates HTML stylés DA Rubis pour les 2 emails sortants — fini le plain text moche. apps/api/app/mails/ ├── _brand.ts : tokens couleur + spacing partagés ├── _layout.tsx : squelette commun (header rubis-deep + footer) ├── checkin_email.tsx : email envoyé À L'USER avec 2 boutons CTA │ Oui (rubis primary) / Non (outlined) └── relance_email.tsx : email envoyé AU CLIENT, body texte du plan + card récap (numéro, montant, échéance, badge retard rubis-deep) Stack : - @react-email/components + @react-email/render - Tous les styles inline (compatible Gmail / Outlook / Apple Mail) - HTML + plain text en fallback (anti-spam, accessibility) mail_dispatcher.ts : - sendRelanceEmail : .html(rendered) + .text(body) - sendCheckinEmail : .html(rendered) + .text(body) - daysLate calculé via clock.now (démo-aware) send_test_email : - Nouveau flag --template=checkin (default) | relance | plain pour tester chaque rendu via Mailpit sans créer de vraie facture. Brand & landing : - "Rubis Sur l'Ongle" → "Rubis sur l'ongle" partout (config, mail, PDF, Stripe appInfo) - Nouvelle env var LANDING_URL (default https://rubis.arthurbarre.fr) - Footer email rend "Rubis sur l'ongle" comme rubis cliquable vers la landing — l'user qui reçoit le mail connaît la marque derrière l'envoi - .env.example mis à jour avec LANDING_URL pour les autres devs Co-Authored-By: Claude Opus 4.7 --- apps/api/.env.example | 8 +- apps/api/app/mails/_brand.ts | 40 ++ apps/api/app/mails/_layout.tsx | 167 +++++++++ apps/api/app/mails/checkin_email.tsx | 213 +++++++++++ apps/api/app/mails/relance_email.tsx | 135 +++++++ apps/api/app/services/mail_dispatcher.ts | 61 ++- apps/api/app/services/stripe.ts | 2 +- apps/api/commands/send_test_email.ts | 106 ++++-- apps/api/config/mail.ts | 4 +- apps/api/database/invoice_pdf_factory.tsx | 2 +- apps/api/package.json | 2 + apps/api/start/env.ts | 3 + pnpm-lock.yaml | 428 ++++++++++++++++++++++ 13 files changed, 1139 insertions(+), 32 deletions(-) create mode 100644 apps/api/app/mails/_brand.ts create mode 100644 apps/api/app/mails/_layout.tsx create mode 100644 apps/api/app/mails/checkin_email.tsx create mode 100644 apps/api/app/mails/relance_email.tsx diff --git a/apps/api/.env.example b/apps/api/.env.example index 65511da..5d4cfc3 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -52,7 +52,7 @@ S3_FORCE_PATH_STYLE=true # Mail (Resend par défaut, Mailpit en fallback dev via MAIL_DRIVER=smtp) #-------------------------------------------------------------------- MAIL_FROM_ADDRESS=rubis@arthurbarre.fr -MAIL_FROM_NAME=Rubis Sur l'Ongle +MAIL_FROM_NAME=Rubis sur l'ongle MAIL_DRIVER=resend RESEND_API_KEY= # Fallback Mailpit (si MAIL_DRIVER=smtp) @@ -70,6 +70,12 @@ MISTRAL_API_KEY= #-------------------------------------------------------------------- WEB_URL=http://localhost:5173 +#-------------------------------------------------------------------- +# Landing publique — lien dans le footer des emails ("Rubis sur l'ongle" +# pointe vers ce domaine). +#-------------------------------------------------------------------- +LANDING_URL=https://rubis.arthurbarre.fr + #-------------------------------------------------------------------- # Auth (refresh tokens) #-------------------------------------------------------------------- diff --git a/apps/api/app/mails/_brand.ts b/apps/api/app/mails/_brand.ts new file mode 100644 index 0000000..8f9c9fd --- /dev/null +++ b/apps/api/app/mails/_brand.ts @@ -0,0 +1,40 @@ +/** + * Tokens de marque partagés par tous les templates email. + * + * On reste sur des hex en dur (pas de CSS vars) parce que les clients mail + * (Gmail, Outlook) ne supportent pas les `--var` dans les `style="..."`. + * Tous les styles sont inline pour la même raison. + */ + +export const BRAND = { + // Palette Rubis (cf. CLAUDE.md → marque) + rubis: '#9F1239', + rubisDeep: '#771328', + rubisLight: '#C9415C', + rubisGlow: '#FBE4EA', + cream: '#FAF7F2', + cream2: '#F5EFE7', + ink: '#1A1410', + ink2: '#4F4640', + ink3: '#8A7F76', + line: '#E8E0D6', + white: '#FFFFFF', + + // Typo — on s'appuie sur les fallbacks system-ui pour la portabilité mail. + fontBody: + "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif", + + // Radius cohérents avec l'app + radiusButton: '6px', + radiusCard: '14px', +} as const + +/** Wrappers d'unités inline-friendly pour pas faire de gros calculs côté