Checkpoint: Ajout de la possibilité d'envoyer des emails via SMTP directement (sans Resend) :

**Modifications de la base de données** :
- Ajout des champs SMTP dans la table `emailConfig` : smtpHost, smtpPort, smtpSecure, smtpUser, smtpPassword

**Backend** :
- Installation de `nodemailer` pour l'envoi SMTP
- Création de `server/_core/smtpSender.ts` avec les fonctions d'envoi SMTP et de test de connexion
- Modification de `server/_core/emailSender.ts` pour supporter les deux méthodes (Resend et SMTP)
- Priorité donnée à SMTP si configuré, sinon fallback sur Resend

**Frontend** :
- Mise à jour de `AdminEmailConfig.tsx` pour permettre le choix entre Resend et SMTP
- Ajout d'un sélecteur de méthode d'envoi (Resend API / SMTP Direct)
- Formulaire de configuration SMTP avec :
  * Hôte SMTP (ex: smtp.gmail.com)
  * Port (587, 465, 25)
  * Sécurité (TLS, SSL, None)
  * Identifiants (utilisateur/mot de passe)
  * Email expéditeur

**Compatibilité** :
- Support des principaux fournisseurs SMTP (Gmail, Outlook, serveurs SMTP personnalisés)
- Instructions pour les mots de passe d'application Gmail
- Mode simulation toujours disponible pour les tests
This commit is contained in:
Manus Sandbox
2025-11-28 06:33:25 -05:00
parent c0446e5ba8
commit 40964c8369
11 changed files with 2193 additions and 48 deletions

View File

@@ -12,6 +12,7 @@
import { ENV } from "./env";
import { notifyOwner } from "./notification";
import { getActiveEmailConfig } from "../db";
import { sendViaSMTP } from "./smtpSender";
interface EmailParams {
to: string;
@@ -137,20 +138,43 @@ export async function sendEmail(params: EmailParams): Promise<boolean> {
return await simulateEmail(params);
}
// Si mode production, tenter d'envoyer via Resend
if (config.mode === 'production' && config.apiKey) {
const sent = await sendViaResend(params, {
apiKey: config.apiKey,
fromEmail: config.fromEmail,
fromName: config.fromName,
});
// Si l'envoi échoue, simuler en fallback
if (!sent) {
return await simulateEmail(params);
// Si mode production
if (config.mode === 'production') {
// Essayer SMTP en priorité si configuré
if (config.provider === 'smtp' && config.smtpHost && config.smtpPort && config.smtpUser && config.smtpPassword) {
const sent = await sendViaSMTP(params, {
host: config.smtpHost,
port: config.smtpPort,
secure: config.smtpSecure || 'tls',
user: config.smtpUser,
password: config.smtpPassword,
fromEmail: config.fromEmail,
fromName: config.fromName,
});
// Si l'envoi échoue, simuler en fallback
if (!sent) {
return await simulateEmail(params);
}
return true;
}
return true;
// Sinon essayer Resend
if (config.provider === 'resend' && config.apiKey) {
const sent = await sendViaResend(params, {
apiKey: config.apiKey,
fromEmail: config.fromEmail,
fromName: config.fromName,
});
// Si l'envoi échoue, simuler en fallback
if (!sent) {
return await simulateEmail(params);
}
return true;
}
}
// Fallback: simuler

103
server/_core/smtpSender.ts Normal file
View File

@@ -0,0 +1,103 @@
/**
* 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
const attachments = params.attachments?.map(att => ({
filename: att.filename,
content: att.content,
contentType: att.contentType,
}));
// Envoyer l'email
const info = await transporter.sendMail({
from: `${config.fromName} <${config.fromEmail}>`,
to: params.to,
subject: params.subject,
html: params.html,
attachments: attachments,
});
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',
};
}
}