1. Personnalisation des templates d'emails avec variables {{lieu}} et {{formateur}} (déjà disponible)
2. Aperçu avant envoi avec données réelles d'un apprenant (modal avec iframe)
3. Email de rappel J-1 avec bouton d'envoi et d'aperçu
Les templates personnalisés supportent maintenant toutes les variables disponibles.
L'aperçu permet de vérifier le rendu final avant l'envoi groupé.
Le rappel J-1 complète le système de communication avec les apprenants.
464 lines
16 KiB
TypeScript
464 lines
16 KiB
TypeScript
/**
|
|
* Service d'envoi d'emails pour les formations
|
|
* Utilise le système de notification Manus pour envoyer des emails
|
|
*/
|
|
|
|
import { generateFormationICS } from "./icsGenerator";
|
|
import { sendEmail as sendEmailViaService, isEmailServiceConfigured } from "./_core/emailSender";
|
|
import { generateEmailFromTemplate } from "./emailTemplateGenerator";
|
|
import { EmailVariables } from "./emailTemplateUtils";
|
|
|
|
interface EmailParams {
|
|
to: string;
|
|
subject: string;
|
|
html: string;
|
|
attachments?: Array<{
|
|
filename: string;
|
|
content: string;
|
|
contentType: string;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Envoie un email via le service d'envoi configuré
|
|
* Utilise Resend si RESEND_API_KEY est configuré, sinon simule l'envoi
|
|
*/
|
|
async function sendEmail(params: EmailParams): Promise<boolean> {
|
|
return await sendEmailViaService(params);
|
|
}
|
|
|
|
/**
|
|
* Template HTML de base pour les emails (avec template personnalisé)
|
|
*/
|
|
async function getEmailTemplate(
|
|
content: string,
|
|
templateType: string = 'inscription',
|
|
variables?: EmailVariables
|
|
): Promise<string> {
|
|
return await generateEmailFromTemplate(templateType, content, variables);
|
|
}
|
|
|
|
/**
|
|
* Envoie un email de confirmation d'inscription avec invitations Outlook pour toutes les dates
|
|
*/
|
|
export async function sendInscriptionConfirmation(params: {
|
|
apprenantEmail: string;
|
|
apprenantNom: string;
|
|
apprenantPrenom: string;
|
|
apprenantFonction: string;
|
|
formationNom: string;
|
|
sequenceNom: string;
|
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
|
lieu: string;
|
|
statut: 'confirmee' | 'liste_attente';
|
|
}): Promise<boolean> {
|
|
const isConfirmed = params.statut === 'confirmee';
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre';
|
|
|
|
// Générer la liste des dates
|
|
const datesHTML = params.dates.map(date => `
|
|
<div class="date-item">
|
|
<strong>Date ${date.ordre} :</strong><br/>
|
|
Du ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}<br/>
|
|
au ${date.dateFin.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Confirmation d'inscription</h2>
|
|
<p>Bonjour ${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom},</p>
|
|
|
|
${isConfirmed
|
|
? '<p><strong>Votre inscription a été confirmée !</strong></p>'
|
|
: '<p><strong>Vous avez été ajouté à la liste d\'attente.</strong></p>'
|
|
}
|
|
|
|
<div class="info-box">
|
|
<h3>Détails de la formation</h3>
|
|
<p><strong>Formation :</strong> ${params.formationNom}</p>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
|
<h4>Dates de formation (${params.dates.length} séance${params.dates.length > 1 ? 's' : ''}) :</h4>
|
|
${datesHTML}
|
|
</div>
|
|
|
|
${isConfirmed
|
|
? `<p>Des invitations Outlook sont jointes à cet email pour chaque date de formation. Merci de les ajouter à votre calendrier pour bloquer votre agenda.</p>
|
|
<p><strong>Important :</strong> Les inscriptions seront bloquées 15 jours avant le début de la séquence. Après cette date, aucune modification ne sera possible.</p>`
|
|
: '<p>Nous vous contacterons dès qu\'une place se libère.</p>'
|
|
}
|
|
|
|
<p>À bientôt pour cette formation !</p>
|
|
`;
|
|
|
|
// Générer une invitation ICS pour chaque date
|
|
const attachments = isConfirmed ? params.dates.map((date, index) => ({
|
|
filename: `invitation_date_${date.ordre}.ics`,
|
|
content: generateFormationICS({
|
|
formationNom: params.formationNom,
|
|
sessionNom: `${params.sequenceNom} - Date ${date.ordre}`,
|
|
dateDebut: date.dateDebut,
|
|
dateFin: date.dateFin,
|
|
lieu: params.lieu,
|
|
apprenantNom: params.apprenantNom,
|
|
apprenantPrenom: params.apprenantPrenom,
|
|
apprenantEmail: params.apprenantEmail,
|
|
}),
|
|
contentType: 'text/calendar',
|
|
})) : undefined;
|
|
|
|
// Préparer les variables pour le remplacement
|
|
const variables: EmailVariables = {
|
|
nomApprenant: params.apprenantNom,
|
|
prenomApprenant: params.apprenantPrenom,
|
|
nomFormation: params.formationNom,
|
|
nomSequence: params.sequenceNom,
|
|
dateDebut: params.dates[0]?.dateDebut.toLocaleDateString('fr-FR') || '',
|
|
dateFin: params.dates[params.dates.length - 1]?.dateFin.toLocaleDateString('fr-FR') || '',
|
|
lieu: params.lieu,
|
|
};
|
|
|
|
return sendEmail({
|
|
to: params.apprenantEmail,
|
|
subject: isConfirmed
|
|
? `Confirmation d'inscription - ${params.formationNom}`
|
|
: `Liste d'attente - ${params.formationNom}`,
|
|
html: await getEmailTemplate(content, 'inscription', variables),
|
|
attachments,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Envoie un email teaser pour une séquence
|
|
*/
|
|
export async function sendTeaserEmail(params: {
|
|
apprenantEmail: string;
|
|
apprenantPrenom: string;
|
|
apprenantNom: string;
|
|
apprenantFonction: string;
|
|
formationNom: string;
|
|
sequenceNom: string;
|
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
|
lieu?: string;
|
|
formateur?: string;
|
|
}): Promise<boolean> {
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : '';
|
|
const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom;
|
|
|
|
const datesHTML = params.dates.map(date => `
|
|
<div class="date-item">
|
|
<strong>Date ${date.ordre} :</strong> ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Votre formation approche !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous sommes ravis de vous accueillir prochainement pour la formation <strong>${params.formationNom}</strong>.</p>
|
|
|
|
<div class="info-box">
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<h4>Dates :</h4>
|
|
${datesHTML}
|
|
${params.lieu ? `<p><strong>Lieu :</strong> ${params.lieu}</p>` : ''}
|
|
${params.formateur ? `<p><strong>Formateur :</strong> ${params.formateur}</p>` : ''}
|
|
</div>
|
|
|
|
<p>Préparez-vous à vivre une expérience enrichissante qui vous permettra de développer vos compétences managériales.</p>
|
|
|
|
<p>À très bientôt !</p>
|
|
`;
|
|
|
|
const variables = {
|
|
nomApprenant: params.apprenantNom,
|
|
prenomApprenant: params.apprenantPrenom,
|
|
nomFormation: params.formationNom,
|
|
nomSequence: params.sequenceNom,
|
|
dateDebut: params.dates[0]?.dateDebut.toLocaleDateString('fr-FR') || '',
|
|
dateFin: params.dates[params.dates.length - 1]?.dateFin.toLocaleDateString('fr-FR') || '',
|
|
lieu: params.lieu || '',
|
|
formateur: params.formateur || '',
|
|
};
|
|
|
|
return sendEmail({
|
|
to: params.apprenantEmail,
|
|
subject: `Votre formation ${params.formationNom} approche !`,
|
|
html: await getEmailTemplate(content, 'teaser', variables),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Envoie un email de rappel J-7
|
|
*/
|
|
export async function sendRappelJ7Email(params: {
|
|
apprenantEmail: string;
|
|
apprenantPrenom: string;
|
|
apprenantNom: string;
|
|
apprenantFonction: string;
|
|
formationNom: string;
|
|
sequenceNom: string;
|
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
|
lieu: string;
|
|
}): Promise<boolean> {
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : '';
|
|
const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom;
|
|
|
|
const datesHTML = params.dates.map(date => `
|
|
<div class="date-item">
|
|
<strong>Date ${date.ordre} :</strong><br/>
|
|
${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Rappel : Votre formation dans 7 jours</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous vous rappelons que votre formation <strong>${params.formationNom}</strong> commence dans 7 jours.</p>
|
|
|
|
<div class="info-box">
|
|
<h3>Informations pratiques</h3>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
<p><strong>Lieu :</strong> ${params.lieu}</p>
|
|
<h4>Dates :</h4>
|
|
${datesHTML}
|
|
</div>
|
|
|
|
<p><strong>Merci de vous présenter à l'heure indiquée.</strong></p>
|
|
|
|
<p>Si vous avez des questions, n'hésitez pas à contacter le service RH.</p>
|
|
|
|
<p>À très bientôt !</p>
|
|
`;
|
|
|
|
const variables = {
|
|
nomApprenant: params.apprenantNom,
|
|
prenomApprenant: params.apprenantPrenom,
|
|
nomFormation: params.formationNom,
|
|
nomSequence: params.sequenceNom,
|
|
dateDebut: params.dates[0]?.dateDebut.toLocaleDateString('fr-FR') || '',
|
|
dateFin: params.dates[params.dates.length - 1]?.dateFin.toLocaleDateString('fr-FR') || '',
|
|
lieu: params.lieu,
|
|
};
|
|
|
|
return sendEmail({
|
|
to: params.apprenantEmail,
|
|
subject: `Rappel J-7 : ${params.formationNom}`,
|
|
html: await getEmailTemplate(content, 'rappel', variables),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Envoie un email de rappel J-1 à un apprenant
|
|
*/
|
|
export async function sendRappelJ1Email(params: {
|
|
apprenantEmail: string;
|
|
apprenantNom: string;
|
|
apprenantPrenom: string;
|
|
apprenantFonction: string;
|
|
formationNom: string;
|
|
sequenceNom: string;
|
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
|
lieu?: string;
|
|
formateur?: string;
|
|
}): Promise<boolean> {
|
|
const fonctionLabel = params.apprenantFonction === 'directeur' ? 'Directeur' :
|
|
params.apprenantFonction === 'chef_service' ? 'Chef de service' : '';
|
|
const salutation = fonctionLabel ? `${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom}` : params.apprenantPrenom;
|
|
|
|
const datesHTML = params.dates.map(date => `
|
|
<div class="date-item">
|
|
<strong>Date ${date.ordre} :</strong> ${date.dateDebut.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})} - ${date.dateFin.toLocaleDateString('fr-FR', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</div>
|
|
`).join('');
|
|
|
|
const content = `
|
|
<h2>Rappel : Votre formation commence demain !</h2>
|
|
<p>Bonjour ${salutation},</p>
|
|
|
|
<p>Nous vous rappelons que votre formation <strong>${params.formationNom}</strong> commence <strong>demain</strong>.</p>
|
|
|
|
<div class="info-box">
|
|
<h3>Informations pratiques</h3>
|
|
<p><strong>Séquence :</strong> ${params.sequenceNom}</p>
|
|
${params.lieu ? `<p><strong>Lieu :</strong> ${params.lieu}</p>` : ''}
|
|
${params.formateur ? `<p><strong>Formateur :</strong> ${params.formateur}</p>` : ''}
|
|
<h4>Dates :</h4>
|
|
${datesHTML}
|
|
</div>
|
|
|
|
<p><strong>Merci de vous présenter à l'heure indiquée.</strong></p>
|
|
<p>N'oubliez pas d'apporter le matériel nécessaire.</p>
|
|
|
|
<p>Si vous avez des questions de dernière minute, n'hésitez pas à contacter le service RH.</p>
|
|
|
|
<p>À demain !</p>
|
|
`;
|
|
|
|
const variables = {
|
|
nomApprenant: params.apprenantNom,
|
|
prenomApprenant: params.apprenantPrenom,
|
|
nomFormation: params.formationNom,
|
|
nomSequence: params.sequenceNom,
|
|
dateDebut: params.dates[0]?.dateDebut.toLocaleDateString('fr-FR') || '',
|
|
dateFin: params.dates[params.dates.length - 1]?.dateFin.toLocaleDateString('fr-FR') || '',
|
|
lieu: params.lieu || '',
|
|
formateur: params.formateur || '',
|
|
};
|
|
|
|
return sendEmail({
|
|
to: params.apprenantEmail,
|
|
subject: `Rappel J-1 : ${params.formationNom} - C'est demain !`,
|
|
html: await getEmailTemplate(content, 'rappel', variables),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Envoie des emails groupés à tous les inscrits d'une séquence
|
|
*/
|
|
export async function sendGroupEmail(params: {
|
|
recipients: Array<{ email: string; prenom: string; nom: string; fonction: string }>;
|
|
formationNom: string;
|
|
sequenceNom: string;
|
|
type: 'teaser' | 'rappel' | 'rappel_j1';
|
|
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
|
|
lieu?: string;
|
|
formateur?: string;
|
|
}): Promise<{ sent: number; failed: number }> {
|
|
let sent = 0;
|
|
let failed = 0;
|
|
|
|
for (const recipient of params.recipients) {
|
|
try {
|
|
if (params.type === 'teaser') {
|
|
await sendTeaserEmail({
|
|
apprenantEmail: recipient.email,
|
|
apprenantPrenom: recipient.prenom,
|
|
apprenantNom: recipient.nom,
|
|
apprenantFonction: recipient.fonction,
|
|
formationNom: params.formationNom,
|
|
sequenceNom: params.sequenceNom,
|
|
dates: params.dates,
|
|
lieu: params.lieu,
|
|
formateur: params.formateur,
|
|
});
|
|
} else if (params.type === 'rappel') {
|
|
await sendRappelJ7Email({
|
|
apprenantEmail: recipient.email,
|
|
apprenantPrenom: recipient.prenom,
|
|
apprenantNom: recipient.nom,
|
|
apprenantFonction: recipient.fonction,
|
|
formationNom: params.formationNom,
|
|
sequenceNom: params.sequenceNom,
|
|
dates: params.dates,
|
|
lieu: params.lieu || '',
|
|
});
|
|
} else {
|
|
// rappel_j1
|
|
await sendRappelJ1Email({
|
|
apprenantEmail: recipient.email,
|
|
apprenantPrenom: recipient.prenom,
|
|
apprenantNom: recipient.nom,
|
|
apprenantFonction: recipient.fonction,
|
|
formationNom: params.formationNom,
|
|
sequenceNom: params.sequenceNom,
|
|
dates: params.dates,
|
|
lieu: params.lieu,
|
|
formateur: params.formateur,
|
|
});
|
|
}
|
|
sent++;
|
|
} catch (error) {
|
|
console.error(`Erreur envoi email à ${recipient.email}:`, error);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
return { sent, failed };
|
|
}
|
|
|
|
/**
|
|
* Envoie un email de réinitialisation de mot de passe
|
|
*/
|
|
export async function sendPasswordResetEmail(params: {
|
|
apprenantEmail: string;
|
|
apprenantNom: string;
|
|
apprenantPrenom: string;
|
|
resetLink: string;
|
|
}): Promise<boolean> {
|
|
const content = `
|
|
<h2>Réinitialisation de mot de passe</h2>
|
|
<p>Bonjour ${params.apprenantPrenom} ${params.apprenantNom},</p>
|
|
|
|
<p>Vous avez demandé la réinitialisation de votre mot de passe pour votre compte Manager Itinova.</p>
|
|
|
|
<p>Cliquez sur le bouton ci-dessous pour créer un nouveau mot de passe :</p>
|
|
|
|
<div style="text-align: center; margin: 30px 0;">
|
|
<a href="${params.resetLink}" class="button">Réinitialiser mon mot de passe</a>
|
|
</div>
|
|
|
|
<p>Ou copiez ce lien dans votre navigateur :</p>
|
|
<div style="background-color: white; padding: 15px; border-radius: 4px; word-break: break-all; font-family: monospace; font-size: 12px; border: 1px solid #e5e7eb;">
|
|
${params.resetLink}
|
|
</div>
|
|
|
|
<div class="info-box" style="background-color: #fef3c7; border-left-color: #f59e0b;">
|
|
<p><strong>⚠️ Important :</strong></p>
|
|
<ul>
|
|
<li>Ce lien est valide pendant <strong>24 heures</strong> seulement</li>
|
|
<li>Il ne peut être utilisé qu'<strong>une seule fois</strong></li>
|
|
<li>Si vous n'avez pas demandé cette réinitialisation, ignorez cet email</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<p>Pour toute question, contactez le service RH.</p>
|
|
`;
|
|
|
|
return sendEmail({
|
|
to: params.apprenantEmail,
|
|
subject: 'Réinitialisation de votre mot de passe - Manager Itinova',
|
|
html: await getEmailTemplate(content, 'reset_password'),
|
|
});
|
|
}
|