Files
formation-manager-itinova/server/_core/smtpSender.ts
Manus feee0d803e Checkpoint: V1.22 - Correction complète des invitations ICS : envoi des 3 fichiers + encodage UTF-8 correct
## Problèmes résolus

### Bug 1 : Seulement 2 invitations sur 3 envoyées
**Cause :** Tentative de conversion base64 incorrecte qui corrompait la première invitation
**Solution :** Conversion explicite Buffer → base64 avec encodage UTF-8 pour toutes les invitations

### Bug 2 : Fichiers ICS corrompus (erreur Outlook "Nous n'avons pas pu importer le calendrier")
**Cause :** Encodage incorrect lors de la transmission SMTP
**Solution :** Conversion UTF-8 → base64 explicite avec contentType correct

## Modifications techniques

- `server/emailService.ts` : Conversion explicite en base64 UTF-8 pour chaque invitation ICS
- `server/_core/smtpSender.ts` : Ajout de logs détaillés pour tracer les pièces jointes
- ContentType : `text/calendar; charset=utf-8` (sans method=REQUEST qui posait problème)

## Déploiement

 Serveur TEST (108.143.67.56) - Validé
 Serveur PROD (98.71.216.234) - Déployé

Les apprenants reçoivent maintenant les 3 invitations ICS et peuvent les importer correctement dans Outlook.
2026-02-26 10:35:14 -05:00

117 lines
3.1 KiB
TypeScript

/**
* Service d'envoi d'emails via SMTP avec nodemailer
*/
import nodemailer from 'nodemailer';
import type { Transporter } from 'nodemailer';
interface EmailParams {
to: string;
subject: string;
html: string;
attachments?: Array<{
filename: string;
content: string;
contentType: string;
}>;
}
interface SMTPConfig {
host: string;
port: number;
secure: 'none' | 'tls' | 'ssl';
user: string;
password: string;
fromEmail: string;
fromName: string;
}
/**
* Crée un transporteur nodemailer à partir de la configuration SMTP
*/
function createTransporter(config: SMTPConfig): Transporter {
const secure = config.secure === 'ssl'; // true pour SSL (port 465), false pour TLS/STARTTLS
return nodemailer.createTransport({
host: config.host,
port: config.port,
secure: secure,
auth: {
user: config.user,
pass: config.password,
},
// Options supplémentaires pour améliorer la compatibilité
tls: {
// Ne pas échouer sur les certificats invalides (à utiliser avec précaution en production)
rejectUnauthorized: false,
},
});
}
/**
* Envoie un email via SMTP
*/
export async function sendViaSMTP(params: EmailParams, config: SMTPConfig): Promise<boolean> {
try {
const transporter = createTransporter(config);
// Préparer les pièces jointes
console.log(`[SMTP] Nombre de pièces jointes reçues: ${params.attachments?.length || 0}`);
if (params.attachments) {
params.attachments.forEach((att, idx) => {
console.log(`[SMTP] Attachment ${idx + 1}: ${att.filename}, contentType: ${att.contentType}, size: ${att.content.length} chars`);
});
}
const attachments = params.attachments?.map(att => ({
filename: att.filename,
content: att.content,
encoding: 'base64' as const,
contentType: att.contentType,
}));
console.log(`[SMTP] Nombre de pièces jointes préparées pour nodemailer: ${attachments?.length || 0}`);
// Envoyer l'email
const mailOptions = {
from: `${config.fromName} <${config.fromEmail}>`,
to: params.to,
subject: params.subject,
html: params.html,
attachments: attachments,
};
console.log(`[SMTP] Envoi de l'email avec ${mailOptions.attachments?.length || 0} pièce(s) jointe(s)`);
const info = await transporter.sendMail(mailOptions);
console.log('[Email] Email envoyé via SMTP:', info.messageId);
return true;
} catch (error) {
console.error('[Email] Erreur lors de l\'envoi via SMTP:', error);
return false;
}
}
/**
* Teste la connexion SMTP
*/
export async function testSMTPConnection(config: SMTPConfig): Promise<{ success: boolean; message: string }> {
try {
const transporter = createTransporter(config);
// Vérifier la connexion
await transporter.verify();
return {
success: true,
message: 'Connexion SMTP réussie',
};
} catch (error) {
console.error('[Email] Erreur de connexion SMTP:', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Erreur de connexion SMTP',
};
}
}