espace-paie-odentas/lib/emailTemplateService.ts

994 lines
No EOL
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// lib/emailTemplateService.ts - Version modernisée
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
import { readFileSync } from "fs";
import { join } from "path";
// Importer la build ESM/UMD de Handlebars pour éviter l'avertissement webpack sur require.extensions
import Handlebars from 'handlebars/dist/handlebars.js';
import { emailLogger, type EmailType, extractSesDataFromResponse } from './emailLoggingService';
// Couleurs standardisées pour tous les emails
const STANDARD_COLORS = {
HEADER: '#171424',
BUTTON: '#efc543',
BUTTON_HOVER: '#d4a834',
BUTTON_TEXT: '#000000',
ALERT_RED: '#EF4444',
} as const;
export interface EmailConfigV2 {
type: EmailTypeV2;
toEmail: string;
ccEmail?: string;
subject?: string; // Rendu optionnel, car peut être défini dans le template
data: EmailDataV2;
}
export type EmailTypeV2 =
| 'contract-created'
| 'contract-updated'
| 'contract-cancelled'
| 'employee-created' // Ajout du nouveau type
| 'invitation'
| 'auto-declaration-invitation' // Nouveau type pour l'invitation auto-déclaration
| 'invoice'
| 'signature-request'
| 'signature-request-employer'
| 'signature-request-employee'
| 'bulk-signature-notification' // Nouveau type pour notification de signatures en masse
| 'salary-transfer-notification' // Nouveau type pour notification d'appel à virement
| 'notification'
// Accès / habilitations
| 'account-activation'
| 'access-updated'
| 'access-revoked'
// Sécurité compte
| 'password-created'
| 'password-changed'
| 'twofa-enabled'
| 'twofa-disabled';
export interface EmailDataV2 {
firstName?: string;
organizationName?: string;
employeeName?: string;
contractReference?: string;
startDate?: string;
endDate?: string;
profession?: string;
amount?: string;
ctaUrl?: string;
customMessage?: string;
// Ajout des champs pour la carte d'info
companyName?: string;
employerCode?: string;
userName?: string;
productionName?: string;
supportUrl?: string;
// Ajout des champs pour les factures et virements
invoiceNumber?: string;
invoiceDate?: string;
sepaDate?: string;
paymentMethod?: string;
invoiceType?: string;
// Ajout des champs pour les notifications de signatures en masse
contractCount?: number;
handlerName?: string;
status?: string;
// Ajout des champs pour les appels à virement
totalAmount?: string;
periodLabel?: string;
deadline?: string;
transferReference?: string;
[key: string]: unknown;
}
interface EmailTemplateV2 {
subject: string;
title: string;
mainMessage: string;
greeting?: string;
closingMessage?: string;
ctaText?: string;
ctaUrl?: string; // URL du bouton CTA
footerText: string;
preheaderText: string;
// Configuration des cartes
infoCard?: Array<{ label: string; key: string; }>;
detailsCard?: {
title: string;
rows: Array<{ label: string; key: string; }>;
};
bankCard?: {
title: string;
subtitle?: string;
conditionKey: string; // Clé pour vérifier la condition
conditionValue: string; // Valeur attendue
additionalConditionKey?: string;
additionalConditionValue?: string;
rows: Array<{ label: string; value: string; }>;
disclaimer?: string;
};
// Couleurs et indicateurs
colors: {
headerColor: string;
titleColor: string;
buttonColor: string;
buttonTextColor: string;
cardBorder: string;
cardBackgroundColor: string;
cardTitleColor: string;
alertIndicatorColor?: string;
};
}
const EMAIL_TEMPLATES_V2: Record<EmailTypeV2, EmailTemplateV2> = {
'password-created': {
subject: 'Mot de passe créé sur votre compte',
title: 'Mot de passe créé',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Vous venez de créer un mot de passe pour votre compte Odentas Paie.',
closingMessage: 'Si vous nêtes pas à lorigine de cette action, <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">contactez le support</a> immédiatement.',
ctaText: 'Gérer ma sécurité',
footerText: 'Vous recevez cet e-mail suite à une modification de la sécurité de votre compte.',
preheaderText: 'Mot de passe créé · Sécurisez votre compte',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Utilisateur', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Détails',
rows: [
{ label: 'Date', key: 'eventDate' },
{ label: 'Plateforme', key: 'platform' },
]
}
},
'password-changed': {
subject: 'Votre mot de passe a été modifié',
title: 'Mot de passe modifié',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Votre mot de passe a été modifié avec succès.',
closingMessage: 'Si vous nêtes pas à lorigine de cette modification, <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">contactez le support</a> immédiatement.',
ctaText: 'Gérer ma sécurité',
footerText: 'Vous recevez cet e-mail pour confirmer une modification de mot de passe.',
preheaderText: 'Mot de passe modifié · Vérifiez la sécurité de votre compte',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#F59E0B',
},
infoCard: [
{ label: 'Utilisateur', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Détails',
rows: [
{ label: 'Date', key: 'eventDate' },
{ label: 'Plateforme', key: 'platform' },
]
}
},
'twofa-enabled': {
subject: '2FA activée sur votre compte',
title: 'Double authentification activée',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'La double authentification (2FA) est maintenant activée sur votre compte.',
ctaText: 'Gérer ma sécurité',
footerText: 'Vous recevez cet e-mail pour confirmer lactivation de la 2FA.',
preheaderText: '2FA activée · Sécurité renforcée',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Utilisateur', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Détails',
rows: [
{ label: 'Date', key: 'eventDate' },
{ label: 'Plateforme', key: 'platform' },
]
}
},
'twofa-disabled': {
subject: '2FA désactivée sur votre compte',
title: 'Double authentification désactivée',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'La double authentification (2FA) a été désactivée sur votre compte.',
closingMessage: 'Nous recommandons de la réactiver pour une sécurité optimale. <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">Contactez le support</a> si ce nétait pas vous.',
ctaText: 'Gérer ma sécurité',
footerText: 'Vous recevez cet e-mail pour confirmer la désactivation de la 2FA.',
preheaderText: '2FA désactivée · Votre sécurité est réduite',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: STANDARD_COLORS.ALERT_RED,
},
infoCard: [
{ label: 'Utilisateur', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Détails',
rows: [
{ label: 'Date', key: 'eventDate' },
{ label: 'Plateforme', key: 'platform' },
]
}
},
'account-activation': {
subject: 'Activez votre espace Odentas Paie {{organizationName}}',
title: 'Activez votre espace',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Bienvenue ! Vous avez été invité à rejoindre {{organizationName}} sur Odentas Paie.',
ctaText: 'Activer mon compte',
footerText: 'Vous recevez cet e-mail car un accès vous a été créé sur Odentas Paie.',
preheaderText: 'Activation de votre espace · {{organizationName}} · Activez maintenant',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Organisation', key: 'organizationName' },
{ label: 'Votre email', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Informations',
rows: [
{ label: 'Plateforme', key: 'platform' },
{ label: 'Action requise', key: 'actionRequired' },
]
}
},
'access-updated': {
subject: 'Vos habilitations ont été modifiées {{organizationName}}',
title: 'Habilitation mise à jour',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Votre rôle au sein de {{organizationName}} a été modifié.',
ctaText: 'Voir vos accès',
footerText: 'Vous recevez cet e-mail suite à une modification de vos droits daccès.',
preheaderText: 'Habilitation mise à jour · Nouveau rôle: {{newRole}}',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#F59E0B',
},
infoCard: [
{ label: 'Organisation', key: 'organizationName' },
{ label: 'Utilisateur', key: 'userEmail' },
],
detailsCard: {
title: 'Détails de la modification',
rows: [
{ label: 'Ancien rôle', key: 'oldRole' },
{ label: 'Nouveau rôle', key: 'newRole' },
{ label: 'Modifié par', key: 'updatedBy' },
{ label: 'Date', key: 'updateDate' },
]
}
},
'access-revoked': {
subject: 'Votre accès a été révoqué {{organizationName}}',
title: 'Accès révoqué',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Votre accès à {{organizationName}} a été révoqué.',
closingMessage: 'Si vous pensez quil sagit dune erreur, <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">contactez le support</a>.',
ctaText: 'Contacter le support',
footerText: 'Vous recevez cet e-mail pour vous informer de la révocation de votre accès.',
preheaderText: 'Accès révoqué · Contactez le support en cas derreur',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: STANDARD_COLORS.ALERT_RED,
},
infoCard: [
{ label: 'Organisation', key: 'organizationName' },
{ label: 'Utilisateur', key: 'userEmail' },
{ label: 'Statut', key: 'status' },
],
detailsCard: {
title: 'Détails de la révocation',
rows: [
{ label: 'Date de révocation', key: 'revocationDate' },
{ label: 'Révoqué par', key: 'revokedBy' },
{ label: 'Raison', key: 'reason' },
]
}
},
'contract-created': {
subject: 'Nouveau CDDU créé - {{employeeName}}',
title: 'Nouveau contrat CDDU',
greeting: 'Bonjour {{userName}},',
mainMessage: 'Un nouveau contrat CDDU a été créé sur votre Espace Paie Odentas.',
ctaText: 'Voir le contrat',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Nouveau contrat créé · Consultez et téléchargez votre contrat',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E', // Vert pour création
},
infoCard: [
{ label: 'Votre structure', key: 'companyName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails du contrat',
rows: [
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Poste', key: 'profession' },
{ label: 'Date de début', key: 'startDate' },
{ label: 'Référence', key: 'contractReference' }
]
}
},
'employee-created': {
subject: 'Nouveau salarié créé - {{employeeName}}',
title: 'Nouveau salarié créé',
greeting: 'Bonjour {{userName}},',
mainMessage: 'Un nouveau salarié a été créé avec succès dans votre Espace Paie Odentas.',
ctaText: 'Voir la fiche du salarié',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Nouveau salarié créé · Consultez la fiche du salarié',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#10B981', // Vert émeraude pour nouveau salarié
},
infoCard: [
{ label: 'Votre structure', key: 'companyName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails du salarié',
rows: [
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Email', key: 'email' },
{ label: 'Matricule', key: 'matricule' },
]
}
},
'contract-updated': {
subject: 'CDDU modifié - {{employeeName}}',
title: 'Contrat CDDU modifié',
greeting: 'Bonjour {{userName}},',
mainMessage: 'Votre contrat CDDU a été modifié sur votre Espace Paie Odentas.',
ctaText: 'Voir les modifications',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Contrat modifié · Consultez les changements',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#F59E0B', // Orange pour modification
},
infoCard: [
{ label: 'Votre structure', key: 'companyName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails du contrat',
rows: [
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Poste', key: 'profession' },
{ label: 'Date de début', key: 'startDate' },
{ label: 'Référence', key: 'contractReference' }
]
}
},
'contract-cancelled': {
subject: 'CDDU annulé - {{employeeName}}',
title: 'CDDU annulé',
greeting: 'Bonjour {{userName}},',
mainMessage: 'Votre contrat CDDU a été annulé.',
closingMessage: 'Pour votre sécurité, ne partagez jamais ce message. <br>Si vous n\'êtes pas à l\'origine de cette annulation, <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">contactez le support</a>.<br><br>L\'équipe Odentas vous remercie pour votre confiance.',
ctaText: 'Accès à l\'Espace Paie',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'CDDU annulé pour {{employeeName}} · Réf {{contractReference}} · Plus d\'informations disponibles',
colors: {
headerColor: '#efc543',
titleColor: '#0F172A',
buttonColor: '#efc543',
buttonTextColor: '#000000',
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: STANDARD_COLORS.ALERT_RED,
},
infoCard: [
{ label: 'Votre structure', key: 'companyName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' }, // Note: 'handlerName' is new
],
detailsCard: {
title: 'Détails du contrat',
rows: [
{ label: 'Référence', key: 'contractReference' },
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Poste', key: 'profession' },
{ label: 'Date de début', key: 'startDate' },
{ label: 'Production', key: 'productionName' },
]
}
},
'invitation': {
subject: 'Invitation à rejoindre {{organizationName}}',
title: 'Vous êtes invité',
mainMessage: 'Vous avez été invité à rejoindre l\'organisation {{organizationName}} sur Odentas.',
ctaText: 'Accepter l\'invitation',
footerText: 'Vous recevez cet e-mail car vous avez été invité sur Odentas.',
preheaderText: 'Invitation reçue · Rejoignez votre équipe',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#F0F9FF',
cardBorder: '#BAE6FD',
cardTitleColor: '#0369A1'
}
},
'auto-declaration-invitation': {
subject: 'Complétez votre dossier d\'embauche - Documents requis',
title: 'Complétez votre dossier d\'embauche',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Dans le cadre de votre projet d\'embauche géré par les services d\'Odentas pour le compte de {{organizationName}}, nous vous invitons à compléter votre dossier.\n\nPour finaliser votre embauche, nous avons besoin que vous nous transmettiez certaines informations personnelles et justificatifs.',
closingMessage: 'Important : Ce lien d\'accès expire dans 7 jours. Pensez à compléter votre dossier avant cette date.<br><br>Besoin d\'aide ? N\'hésitez pas à nous contacter à <a href="mailto:paie@odentas.fr" style="color:#0B5FFF;">paie@odentas.fr</a> pour toute question.',
ctaText: 'Compléter mon dossier',
footerText: 'Ce lien est personnel et sécurisé. Ne le partagez avec personne d\'autre.<br><br>Vous recevez cet e-mail car votre employeur ou futur employeur est client de Odentas, pour vous notifier d\'une action sur votre contrat de travail ou votre projet d\'embauche avec cet employeur. Ce mail ne constitue pas une promesse d\'embauche.',
preheaderText: 'Dossier d\'embauche · Complétez vos informations',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: '#059669',
buttonTextColor: '#FFFFFF',
cardBackgroundColor: '#F0F9FF',
cardBorder: '#E0F2FE',
cardTitleColor: '#0C4A6E'
},
infoCard: [
{ label: 'Votre employeur', key: 'organizationName' },
{ label: 'Votre matricule', key: 'matricule' }
]
},
'invoice': {
subject: 'Nouvelle facture - {{amount}}',
title: 'Nouvelle facture',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: '{{{customMessage}}}',
closingMessage: 'Si vous avez des questions concernant cette facture, <a href="mailto:paie@odentas.fr" style="color:#0B5FFF; text-decoration:none;">contactez-nous à paie@odentas.fr</a>.<br><br>L\'équipe Odentas vous remercie pour votre confiance.',
ctaText: 'Voir la facture',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Nouvelle facture · Accédez à votre espace',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E'
},
infoCard: [
{ label: 'Votre structure', key: 'organizationName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' }
],
detailsCard: {
title: 'Détails de la facture',
rows: [
{ label: 'Numéro', key: 'invoiceNumber' },
{ label: 'Montant', key: 'amount' },
{ label: 'Date de facture', key: 'invoiceDate' },
{ label: 'Prélèvement SEPA', key: 'sepaDate' }
]
},
bankCard: {
title: 'Coordonnées bancaires pour le virement',
conditionKey: 'invoiceType',
conditionValue: 'paie_mensuelle',
additionalConditionKey: 'paymentMethod',
additionalConditionValue: 'virement',
rows: [
{ label: 'Bénéficiaire', value: 'Odentas Media SAS' },
{ label: 'IBAN', value: 'FR7616958000014277434166014' },
{ label: 'BIC', value: 'QNTOFRP1XXX' },
{ label: 'Libellé', value: '{{invoiceNumber}}' }
],
disclaimer: 'Ce compte ne peut pas recevoir les virements de salaires de vos salariés.'
}
},
'signature-request': {
subject: 'Signature requise - {{employeeName}}',
title: 'Signature électronique requise',
mainMessage: 'Votre signature est requise pour finaliser le contrat.',
ctaText: 'Signer maintenant',
footerText: 'Vous recevez cet e-mail car votre signature est requise.',
preheaderText: 'Signature requise · Finalisez votre contrat',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E'
},
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
infoCard: [
{ label: 'Votre structure', key: 'organizationName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails de la signature',
rows: [
{ label: 'Document', key: 'documentType' },
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Référence', key: 'contractReference' },
{ label: 'Statut', key: 'status' },
]
}
},
'signature-request-employer': {
subject: 'Signature requise {{employeeName}}',
title: 'Signature électronique requise',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Un document nécessite votre signature électronique en tant quemployeur.',
ctaText: 'Signer le document',
footerText: 'Vous recevez cet e-mail car votre signature est requise sur un document.',
preheaderText: 'Signature électronique requise · Signez en tant quemployeur',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Votre structure', key: 'organizationName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails du document',
rows: [
{ label: 'Document', key: 'documentType' },
{ label: 'Salarié', key: 'employeeName' },
{ label: 'Référence', key: 'contractReference' },
{ label: 'Statut', key: 'status' },
]
}
},
'signature-request-employee': {
subject: 'Signature requise {{documentType}}',
title: 'Signature électronique requise',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Ce rappel vous est envoyé par votre employeur, votre contrat de travail est en attente de signature.',
ctaText: 'Signer le document',
closingMessage: 'Nous restons à votre disposition à paie@odentas.fr si vous avez besoin d\'assistance.',
footerText: 'Vous recevez cet e-mail car votre employeur est client de Odentas, pour vous notifier d\'une action sur votre contrat de travail avec cet employeur.',
preheaderText: 'Signature électronique requise · Signez votre document',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Votre employeur', key: 'organizationName' },
{ label: 'Votre matricule', key: 'matricule' },
],
detailsCard: {
title: 'Détails du document',
rows: [
{ label: 'Référence', key: 'contractReference' },
{ label: 'Profession', key: 'profession' },
{ label: 'Date de début', key: 'startDate' },
{ label: 'Production', key: 'productionName' },
]
}
},
'bulk-signature-notification': {
subject: 'Contrats à signer {{contractCount}} signature{{#if (gt contractCount 1)}}s{{/if}} en attente',
title: 'Contrats en attente de signature',
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
mainMessage: 'Vous avez {{contractCount}} contrat{{#if (gt contractCount 1)}}s{{/if}} en attente de signature électronique.<br><br>Pour consulter et signer vos contrats, cliquez sur le bouton ci-dessous :',
closingMessage: 'Si vous avez des questions, n\'hésitez pas à nous contacter à <a href="mailto:paie@odentas.fr" style="color:#0B5FFF; text-decoration:none;">paie@odentas.fr</a>.',
ctaText: 'Accéder aux signatures',
ctaUrl: 'https://paie.odentas.fr/signatures-electroniques',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Contrats à signer · {{contractCount}} signature{{#if (gt contractCount 1)}}s{{/if}} requise{{#if (gt contractCount 1)}}s{{/if}}',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#FFFFFF',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#6366F1',
},
infoCard: [
{ label: 'Votre structure', key: 'organizationName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Signatures en attente',
rows: [
{ label: 'Nombre de contrats', key: 'contractCount' },
{ label: 'Statut', key: 'status' },
]
}
},
'salary-transfer-notification': {
subject: 'Nouvel appel à virement - {{periodLabel}}',
title: 'Nouvel appel à virement',
greeting: '{{#if firstName}}👋 Bonjour {{firstName}},{{/if}}',
mainMessage: 'Vous trouverez ci-dessous les détails du virement à nous transférer pour le paiement des rémunérations de vos salariés.<br><br>L\'appel à virement est disponible sur votre Espace Paie, en cliquant sur le bouton ci-dessous.',
closingMessage: 'Le montant correspond au cumul des salaires nets de la période concernée, après prélèvement à la source.<br><br>Dès réception du virement, nous procéderons sous 24 heures ouvrées aux versements individuels des salaires. Vos salariés et vous-même recevrez une notification.<br><br>Les cotisations (ainsi que le prélèvement à la source) sont prélevées directement sur votre compte bancaire par les organismes concernés.<br><br>N\'hésitez pas à répondre à cet e-mail si vous avez besoin d\'assistance.<br><br>Merci pour votre confiance,<br>L\'équipe Odentas.',
ctaText: 'Télécharger l\'Appel à Virement',
ctaUrl: 'https://paie.odentas.fr/virements-salaires/',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Appel à virement · {{periodLabel}}',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#F0F0F5',
cardBorder: '#E5E7EB',
cardTitleColor: '#0F172A',
alertIndicatorColor: '#22C55E',
},
infoCard: [
{ label: 'Votre structure', key: 'organizationName' },
{ label: 'Votre code employeur', key: 'employerCode' },
{ label: 'Votre gestionnaire', key: 'handlerName' },
],
detailsCard: {
title: 'Détails du virement',
rows: [
{ label: 'Montant à virer', key: 'totalAmount' },
{ label: 'Période concernée', key: 'periodLabel' },
]
},
bankCard: {
title: 'Nos coordonnées bancaires',
subtitle: 'Attention, le compte bancaire ci-dessous est uniquement destiné à la gestion des salaires.',
conditionKey: 'showBankInfo',
conditionValue: 'true',
rows: [
{ label: 'Bénéficiaire', value: 'ODENTAS MEDIA SAS' },
{ label: 'IBAN', value: 'FR76 1695 8000 0141 0850 9729 813' },
{ label: 'BIC', value: 'QNTOFRP1XXX' },
{ label: 'Référence', value: '{{transferReference}}' },
],
disclaimer: 'Ce compte bancaire est réservé à la réception des virements de salaires.'
}
},
'notification': {
subject: 'Notification - {{title}}',
title: 'Notification',
mainMessage: 'Vous avez une nouvelle notification.',
footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas, pour vous notifier d\'une action sur votre compte.',
preheaderText: 'Nouvelle notification',
colors: {
headerColor: STANDARD_COLORS.HEADER,
titleColor: '#0F172A',
buttonColor: STANDARD_COLORS.BUTTON,
buttonTextColor: STANDARD_COLORS.BUTTON_TEXT,
cardBackgroundColor: '#F9FAFB',
cardBorder: '#E5E7EB',
cardTitleColor: '#374151'
}
}
};
// Register Handlebars helpers
Handlebars.registerHelper('gt', function(a: any, b: any) {
return a > b;
});
function replaceVariables(template: string, data: EmailDataV2): string {
const compiled = Handlebars.compile(template);
return compiled(data);
}
// Fonction pour traiter les retours à la ligne dans mainMessage
function processMainMessage(template: string, data: EmailDataV2): string {
const processed = replaceVariables(template, data);
// Convertir les \n en <br> pour l'affichage HTML
return processed.replace(/\n/g, '<br>');
}
export async function renderUniversalEmailV2(config: EmailConfigV2): Promise<{ subject: string; html: string; text: string; }> {
const templateConfig = EMAIL_TEMPLATES_V2[config.type];
if (!templateConfig) {
throw new Error(`Template not found for type: ${config.type}`);
}
// Enrichir les données avec des valeurs par défaut ou statiques
const data: EmailDataV2 = {
handlerName: 'Renaud BREVIERE-ABRAHAM',
supportUrl: 'mailto:paie@odentas.fr',
...config.data,
};
// Debug log pour les factures
if (config.type === 'invoice') {
console.log('[emailTemplateService] Invoice email data:', {
customMessage: data.customMessage,
firstName: data.firstName,
amount: data.amount,
invoiceNumber: data.invoiceNumber
});
}
const templateData = {
subject: replaceVariables(config.subject || templateConfig.subject, data),
title: replaceVariables(templateConfig.title, data),
greeting: templateConfig.greeting ? replaceVariables(templateConfig.greeting, data) : undefined,
mainMessage: processMainMessage(templateConfig.mainMessage, data),
closingMessage: templateConfig.closingMessage ? replaceVariables(templateConfig.closingMessage, data) : undefined,
ctaText: templateConfig.ctaText ? replaceVariables(templateConfig.ctaText, data) : undefined,
footerText: replaceVariables(templateConfig.footerText, data),
preheaderText: replaceVariables(templateConfig.preheaderText, data),
textFallback: `${replaceVariables(templateConfig.title, data)} - ${replaceVariables(templateConfig.mainMessage, data)}`,
ctaUrl: templateConfig.ctaUrl || (config.type === 'invoice' ? 'https://paie.odentas.fr/facturation' : data.ctaUrl),
logoUrl: 'https://newstaging.odentas.fr/wp-content/uploads/2025/08/Odentas-Logo-Bleu-Fond-Transparent-4-1.png',
showInfoCard: !!templateConfig.infoCard,
infoCardRows: templateConfig.infoCard?.map(field => ({
label: field.label,
value: data[field.key] ?? '—'
})),
showDetailsCard: !!templateConfig.detailsCard,
detailsCardTitle: templateConfig.detailsCard?.title,
detailsCardRows: templateConfig.detailsCard?.rows.map(field => ({
label: field.label,
value: data[field.key] ?? '—'
})),
// Logique pour la carte bancaire conditionnelle
showBankCard: templateConfig.bankCard ?
(data[templateConfig.bankCard.conditionKey] === templateConfig.bankCard.conditionValue &&
(!templateConfig.bankCard.additionalConditionKey ||
data[templateConfig.bankCard.additionalConditionKey] === templateConfig.bankCard.additionalConditionValue)) : false,
bankCardTitle: templateConfig.bankCard?.title,
bankCardRows: templateConfig.bankCard?.rows.map(field => ({
label: field.label,
value: replaceVariables(field.value, data)
})),
bankCardDisclaimer: templateConfig.bankCard?.disclaimer,
...templateConfig.colors,
};
const templatePath = join(process.cwd(), 'templates-mails', 'universal-template.html');
const htmlTemplateSource = readFileSync(templatePath, 'utf-8');
const handlebarsTemplate = Handlebars.compile(htmlTemplateSource);
const renderedHtml = handlebarsTemplate(templateData);
return { subject: templateData.subject, html: renderedHtml, text: templateData.textFallback };
}
export async function sendUniversalEmailV2(config: EmailConfigV2): Promise<string> {
console.log('🔍 [SES DEBUG] sendUniversalEmailV2 called with:', {
type: config.type,
toEmail: config.toEmail,
ccEmail: config.ccEmail,
toEmailType: typeof config.toEmail,
ccEmailType: typeof config.ccEmail,
subject: config.subject
});
// Validation des emails
const isValidEmail = (email: string) => {
return email && typeof email === 'string' && email.includes('@') && email.includes('.') && email.trim().length > 0;
};
// Valider l'email principal
if (!isValidEmail(config.toEmail)) {
console.error('🚨 [SES DEBUG] Invalid toEmail detected:', {
toEmail: config.toEmail,
type: typeof config.toEmail,
length: config.toEmail?.length,
isEmpty: config.toEmail === '',
isNull: config.toEmail === null,
isUndefined: config.toEmail === undefined
});
throw new Error(`Invalid email address: "${config.toEmail}"`);
}
// Valider l'email CC s'il existe
if (config.ccEmail && !isValidEmail(config.ccEmail)) {
console.warn(`Invalid CC email address, ignoring: "${config.ccEmail}"`);
config.ccEmail = undefined; // Supprimer l'email CC invalide
}
console.log('🔍 [SES DEBUG] Emails validated, proceeding with send:', {
toEmail: config.toEmail,
ccEmail: config.ccEmail
});
// Rendre l'email pour obtenir HTML/TXT et sujet
const rendered = await renderUniversalEmailV2(config);
// Créer le log d'email AVANT l'envoi
const logId = await emailLogger.logEmail({
senderEmail: process.env.AWS_SES_FROM || 'paie@odentas.fr',
recipientEmail: config.toEmail,
ccEmails: config.ccEmail ? [config.ccEmail] : undefined,
subject: rendered.subject,
htmlContent: rendered.html,
textContent: rendered.text,
emailType: config.type as EmailType,
templateName: 'universal-v2',
templateData: config.data,
emailStatus: 'sending',
organizationId: typeof config.data.organizationId === 'string' ? config.data.organizationId : undefined,
contractId: typeof config.data.contractId === 'string' ? config.data.contractId : undefined,
tags: {
template_version: 'v2',
email_system: 'universal'
},
context: {
subject_preview: rendered.subject.substring(0, 100),
has_cta: !!config.data.ctaUrl,
data_keys: Object.keys(config.data)
}
});
// Configuration SES
const sesClient = new SESClient({ region: process.env.AWS_REGION || 'eu-west-3' });
// Nettoyer l'adresse source pour AWS SES
const rawSource = process.env.AWS_SES_FROM || 'paie@odentas.fr';
const cleanSource = rawSource.replace(/^["']|["']$/g, ''); // Supprimer les guillemets externes
const params = {
Source: cleanSource,
Destination: {
ToAddresses: [config.toEmail] as string[],
...(config.ccEmail ? { CcAddresses: [config.ccEmail] as string[] } : {})
},
Message: {
Subject: { Data: rendered.subject, Charset: "UTF-8" },
Body: {
Html: { Data: rendered.html, Charset: "UTF-8" },
Text: { Data: rendered.text, Charset: "UTF-8" }
}
}
};
console.log('🔍 [SES DEBUG] Full SES params before send:', {
SourceRaw: process.env.AWS_SES_FROM,
SourceCleaned: params.Source,
ToAddresses: params.Destination.ToAddresses,
CcAddresses: params.Destination.CcAddresses || 'none',
Subject: params.Message.Subject.Data,
awsRegion: process.env.AWS_REGION || 'eu-west-3',
paramsDestination: JSON.stringify(params.Destination),
allAddresses: [...params.Destination.ToAddresses, ...(params.Destination.CcAddresses || [])]
});
try {
const command = new SendEmailCommand(params);
const response = await sesClient.send(command);
const messageId = (response as any)?.MessageId || '';
console.log(`Email sent successfully to ${config.toEmail} - Type: ${config.type} - MessageId: ${messageId}`);
// Mettre à jour le log avec le succès et l'ID SES
if (logId) {
await emailLogger.updateEmailLog(logId, {
emailStatus: 'sent',
sesMessageId: messageId,
sentAt: new Date()
});
}
return messageId;
} catch (error) {
console.error("Error sending email via SES:", error);
// Mettre à jour le log avec l'erreur
if (logId) {
await emailLogger.updateEmailLog(logId, {
emailStatus: 'failed',
failureReason: error instanceof Error ? error.message : 'Unknown error'
});
}
throw error;
}
}