// lib/emailService.ts import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses"; import { readFileSync } from "fs"; import { join } from "path"; interface EmailOptions { toEmail: string; ccEmail?: string; subject: string; templateName: string; templateData: Record; fromEmail?: string; fromName?: string; } export async function sendEmail({ toEmail, ccEmail, subject, templateName, templateData, fromEmail, fromName }: EmailOptions) { const region = process.env.AWS_REGION || "eu-west-3"; const from = fromEmail || process.env.AWS_SES_FROM; if (!from) { throw new Error("Missing AWS_SES_FROM environment variable"); } try { // Lire le template d'email const templatePath = join(process.cwd(), 'templates-mails', `${templateName}.html`); let html = readFileSync(templatePath, 'utf8'); // Remplacer les variables dans le template Object.entries(templateData).forEach(([key, value]) => { const regex = new RegExp(`{{${key}}}`, 'g'); html = html.replace(regex, value || '—'); }); // Créer le texte brut simple const textContent = Object.entries(templateData) .map(([key, value]) => `${key}: ${value}`) .join('\n'); const ses = new SESClient({ region }); // Nettoyer les emails pour supprimer tout espace ou caractère de contrôle const cleanToEmail = toEmail.replace(/\s+/g, '').trim(); const cleanCcEmail = ccEmail ? ccEmail.replace(/\s+/g, '').trim() : undefined; const destination: any = { ToAddresses: [cleanToEmail] }; // Ne pas ajouter CcAddresses si ccEmail est vide ou invalide if (cleanCcEmail && cleanCcEmail.length > 0 && cleanCcEmail.includes('@')) { destination.CcAddresses = [cleanCcEmail]; } // Construire le champ Source avec validation let sourceField: string; if (fromName) { // Si from contient déjà des chevrons, on les retire d'abord const cleanFrom = from.replace(/[<>\s]/g, '').trim(); sourceField = `${fromName} <${cleanFrom}>`; } else { sourceField = from.replace(/\s+/g, '').trim(); } console.log('[sendEmail] Source field:', sourceField); console.log('[sendEmail] To:', cleanToEmail); console.log('[sendEmail] CC:', cleanCcEmail || 'none'); const cmd = new SendEmailCommand({ Destination: destination, Source: sourceField, Message: { Subject: { Data: subject, Charset: "UTF-8" }, Body: { Html: { Data: html, Charset: "UTF-8" }, Text: { Data: textContent, Charset: "UTF-8" }, }, }, }); await ses.send(cmd); console.log(`Email sent successfully to ${toEmail}${ccEmail && ccEmail.trim() ? ` (CC: ${ccEmail})` : ''}`); } catch (error) { console.error('Error sending email:', error); throw error; } } export async function sendContractNotifications( contractData: any, organizationData: any ) { try { const emailNotifs = organizationData.organization_details?.email_notifs; const emailNotifsCC = organizationData.organization_details?.email_notifs_cc; // Validation des emails const isValidEmail = (email: string) => { return email && email.includes('@') && email.includes('.') && email.trim().length > 0; }; // Use the new universal email system if (emailNotifs && isValidEmail(emailNotifs)) { const { sendUniversalEmailV2 } = await import('./emailTemplateService'); // Valider l'email CC s'il existe const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined; await sendUniversalEmailV2({ type: 'contract-created', toEmail: emailNotifs, ccEmail: validatedCcEmail, subject: `Nouveau CDDU créé - ${contractData.employee_name}`, data: { firstName: organizationData.organization_details?.prenom_contact || "", employeeName: contractData.employee_name, contractReference: contractData.contract_number || contractData.reference, startDate: formatDateFR(contractData.start_date), profession: contractData.profession || contractData.role, organizationName: organizationData.name || contractData.structure, // Ajouts pour la carte d'information companyName: organizationData.name || contractData.structure, employerCode: organizationData.organization_details?.code_employeur || "", userName: organizationData.organization_details?.prenom_contact || "cher client", ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats/${contractData.id}`, customMessage: `Un nouveau contrat CDDU a été créé pour ${contractData.employee_name}.` } }); } else { console.warn('No valid notification email configured for organization:', { orgId: organizationData.id, orgName: organizationData.name, emailNotifs, emailNotifsCC }); } console.log('Contract notifications sent successfully (universal system)'); } catch (error) { console.error('Error sending contract notifications:', error); } } export async function sendContractUpdateNotifications( contractData: any, organizationData: any ) { try { console.log('🔍 [EMAIL DEBUG] sendContractUpdateNotifications called with:', { contractId: contractData?.id, orgId: organizationData?.id, orgName: organizationData?.name, hasOrgDetails: !!organizationData?.organization_details, orgDetails: organizationData?.organization_details }); // Fix: Si organization_details est un tableau, prendre le premier élément let orgDetails = organizationData?.organization_details; if (Array.isArray(orgDetails)) { console.log('🔍 [EMAIL DEBUG] organization_details is array, taking first element'); orgDetails = orgDetails[0] || {}; } const emailNotifs = orgDetails?.email_notifs; const emailNotifsCC = orgDetails?.email_notifs_cc; console.log('🔍 [EMAIL DEBUG] Email addresses found:', { emailNotifs, emailNotifsCC, emailNotifsType: typeof emailNotifs, emailNotifsCCType: typeof emailNotifsCC }); // Use the new universal email system if (emailNotifs) { // Validation des emails const isValidEmail = (email: string) => { return email && email.includes('@') && email.includes('.') && email.trim().length > 0; }; if (isValidEmail(emailNotifs)) { const { sendUniversalEmailV2 } = await import('./emailTemplateService'); // Valider l'email CC s'il existe const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined; console.log('🔍 [EMAIL DEBUG] Sending email with validated addresses:', { toEmail: emailNotifs, ccEmail: validatedCcEmail }); await sendUniversalEmailV2({ type: 'contract-updated', toEmail: emailNotifs, ccEmail: validatedCcEmail, subject: `CDDU modifié - ${contractData.employee_name}`, data: { firstName: orgDetails?.prenom_contact || "", employeeName: contractData.employee_name, contractReference: contractData.contract_number || contractData.reference, startDate: formatDateFR(contractData.start_date), profession: contractData.profession || contractData.role, organizationName: organizationData.name || contractData.structure, // Ajouts pour la carte d'information companyName: organizationData.name || contractData.structure, employerCode: orgDetails?.code_employeur || "", userName: orgDetails?.prenom_contact || "cher client", ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats/${contractData.id}`, customMessage: `Votre contrat CDDU pour ${contractData.employee_name} a été modifié.` } }); } else { console.warn('Invalid notification email for contract update:', { orgId: organizationData.id, orgName: organizationData.name, emailNotifs, emailNotifsCC }); } } else { console.warn('No notification email configured for contract update:', { orgId: organizationData.id, orgName: organizationData.name, hasOrgDetails: !!orgDetails, orgDetails }); } console.log('Contract update notifications sent successfully (universal system)'); } catch (error) { console.error('Error sending contract update notifications:', error); } } export async function sendContractCancellationNotifications( contractData: any, organizationData: any ) { try { const emailNotifs = organizationData.organization_details?.email_notifs; const emailNotifsCC = organizationData.organization_details?.email_notifs_cc; // Use the new universal email system if (emailNotifs) { // Validation des emails const isValidEmail = (email: string) => { return email && email.includes('@') && email.includes('.') && email.trim().length > 0; }; if (isValidEmail(emailNotifs)) { const { sendUniversalEmailV2 } = await import('./emailTemplateService'); // Valider l'email CC s'il existe const validatedCcEmail = (emailNotifsCC && isValidEmail(emailNotifsCC)) ? emailNotifsCC : undefined; await sendUniversalEmailV2({ type: 'contract-cancelled', toEmail: emailNotifs, ccEmail: validatedCcEmail, subject: `CDDU annulé - ${contractData.employee_name}`, data: { firstName: organizationData.organization_details?.prenom_contact || "", employeeName: contractData.employee_name, contractReference: contractData.contract_number || contractData.reference, startDate: formatDateFR(contractData.start_date), profession: contractData.profession || contractData.role, organizationName: organizationData.name || contractData.structure, productionName: contractData.production_name || contractData.analytique || "Production principale", companyName: organizationData.name || contractData.structure, employerCode: organizationData.organization_details?.code_employeur || contractData.employer_code || "", userName: organizationData.organization_details?.prenom_contact || "cher client", ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/contrats`, supportUrl: 'mailto:paie@odentas.fr', customMessage: `Votre contrat CDDU pour ${contractData.employee_name} a été annulé.` } }); } else { console.warn('Invalid notification email for contract cancellation:', { orgId: organizationData.id, orgName: organizationData.name, emailNotifs, emailNotifsCC }); } } console.log('Contract cancellation notifications sent successfully (universal system)'); } catch (error) { console.error('Error sending contract cancellation notifications:', error); } } function formatDateFR(dateStr: string | null): string { if (!dateStr) return "—"; try { return new Date(dateStr).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }); } catch { return dateStr; } } // Fonction pour envoyer une invitation export async function sendInviteEmail({ userEmail, inviteToken, organizationName, fromEmail = 'paie@odentas.fr', fromName = 'Odentas Paie' }: { userEmail: string; inviteToken: string; organizationName: string; fromEmail?: string; fromName?: string; }) { try { const activationUrl = `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/activate?token=${inviteToken}`; const templateData = { activationUrl, organizationName, supportUrl: 'mailto:paie@odentas.fr' }; await sendEmail({ toEmail: userEmail, subject: `Invitation à rejoindre ${organizationName} sur Odentas Paie`, templateName: 'invite', templateData, fromEmail, fromName }); console.log('Invitation sent successfully to:', userEmail); } catch (error) { console.error('Error sending invitation:', error); throw error; } } // Fonction pour envoyer une notification de facture export async function sendInvoiceNotification({ userEmail, invoiceData, fromEmail = 'paie@odentas.fr', fromName = 'Odentas Paie' }: { userEmail: string; invoiceData: any; fromEmail?: string; fromName?: string; }) { try { const templateData = { firstName: invoiceData.firstName || "", invoiceNumber: invoiceData.invoiceNumber, amount: invoiceData.amount, dueDate: invoiceData.dueDate, organizationName: invoiceData.organizationName, ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/facturation`, supportUrl: 'mailto:paie@odentas.fr' }; await sendEmail({ toEmail: userEmail, subject: `Nouvelle facture - ${invoiceData.invoiceNumber}`, templateName: 'facture', templateData, fromEmail, fromName }); console.log('Invoice notification sent successfully to:', userEmail); } catch (error) { console.error('Error sending invoice notification:', error); throw error; } } // Fonction pour envoyer une demande de signature électronique export async function sendSignatureRequest({ userEmail, documentData, fromEmail = 'paie@odentas.fr', fromName = 'Odentas Paie' }: { userEmail: string; documentData: any; fromEmail?: string; fromName?: string; }) { try { const templateData = { firstName: documentData.firstName || "", documentTitle: documentData.documentTitle, organizationName: documentData.organizationName, ctaUrl: documentData.signatureUrl, supportUrl: 'mailto:paie@odentas.fr' }; await sendEmail({ toEmail: userEmail, subject: `Signature électronique requise - ${documentData.documentTitle}`, templateName: 'signature-electronique', templateData, fromEmail, fromName }); console.log('Signature request sent successfully to:', userEmail); } catch (error) { console.error('Error sending signature request:', error); throw error; } }