Checkpoint: Amélioration du système d'envoi d'emails : intégration de Resend pour l'envoi réel d'emails avec fallback vers simulation si non configuré. Documentation complète ajoutée dans EMAIL_SETUP.md pour guider la configuration.
This commit is contained in:
109
EMAIL_SETUP.md
Normal file
109
EMAIL_SETUP.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Configuration de l'envoi d'emails
|
||||
|
||||
## État actuel
|
||||
|
||||
Par défaut, l'application **simule** l'envoi d'emails en créant des notifications pour le propriétaire du projet. Les emails ne sont pas réellement envoyés aux utilisateurs finaux.
|
||||
|
||||
## Configuration de l'envoi réel d'emails
|
||||
|
||||
Pour activer l'envoi réel d'emails, vous devez configurer un service d'envoi d'emails externe. L'application supporte **Resend**, un service moderne et simple d'utilisation.
|
||||
|
||||
### Étape 1 : Créer un compte Resend
|
||||
|
||||
1. Allez sur [https://resend.com](https://resend.com)
|
||||
2. Créez un compte gratuit (100 emails/jour inclus)
|
||||
3. Vérifiez votre adresse email
|
||||
|
||||
### Étape 2 : Obtenir une clé API
|
||||
|
||||
1. Dans le tableau de bord Resend, allez dans **API Keys**
|
||||
2. Cliquez sur **Create API Key**
|
||||
3. Donnez un nom à votre clé (ex: "Manager Itinova Production")
|
||||
4. Copiez la clé API générée (elle commence par `re_`)
|
||||
|
||||
### Étape 3 : Configurer votre domaine (optionnel mais recommandé)
|
||||
|
||||
Pour envoyer des emails depuis votre propre domaine (ex: `noreply@itinova.org`) :
|
||||
|
||||
1. Dans Resend, allez dans **Domains**
|
||||
2. Cliquez sur **Add Domain**
|
||||
3. Entrez votre domaine (ex: `itinova.org`)
|
||||
4. Suivez les instructions pour ajouter les enregistrements DNS
|
||||
5. Attendez la vérification (quelques minutes à quelques heures)
|
||||
|
||||
**Note:** Sans domaine vérifié, les emails seront envoyés depuis `noreply@manusvm.computer`
|
||||
|
||||
### Étape 4 : Ajouter les secrets dans Manus
|
||||
|
||||
1. Ouvrez l'interface de gestion de votre projet Manus
|
||||
2. Allez dans **Settings** → **Secrets**
|
||||
3. Ajoutez les secrets suivants :
|
||||
|
||||
| Nom du secret | Valeur | Description |
|
||||
|---------------|--------|-------------|
|
||||
| `RESEND_API_KEY` | `re_xxxxx...` | Votre clé API Resend |
|
||||
| `RESEND_FROM_EMAIL` | `noreply@itinova.org` | Adresse email d'envoi (doit être vérifiée dans Resend) |
|
||||
|
||||
4. Sauvegardez les modifications
|
||||
5. Redémarrez l'application (elle détectera automatiquement la nouvelle configuration)
|
||||
|
||||
### Étape 5 : Tester l'envoi
|
||||
|
||||
1. Allez dans **Utilisateurs**
|
||||
2. Cliquez sur le bouton de réinitialisation de mot de passe pour un utilisateur
|
||||
3. Vérifiez que l'email a bien été reçu
|
||||
|
||||
## Vérification de la configuration
|
||||
|
||||
Vous pouvez vérifier si l'envoi d'emails réel est activé en consultant les logs du serveur :
|
||||
|
||||
- **Emails simulés** : `[Email] RESEND_API_KEY non configurée, email simulé`
|
||||
- **Emails réels** : `[Email] Email envoyé via Resend: <id>`
|
||||
|
||||
## Alternatives à Resend
|
||||
|
||||
Si vous préférez utiliser un autre service d'envoi d'emails, vous pouvez modifier le fichier `server/_core/emailSender.ts` pour intégrer :
|
||||
|
||||
- **SendGrid** : Service populaire avec 100 emails/jour gratuits
|
||||
- **Mailgun** : Service robuste avec 5000 emails/mois gratuits
|
||||
- **AWS SES** : Service Amazon très économique pour gros volumes
|
||||
- **Votre propre serveur SMTP** : Configuration personnalisée
|
||||
|
||||
## Limites et quotas
|
||||
|
||||
### Plan gratuit Resend
|
||||
- 100 emails par jour
|
||||
- 3000 emails par mois
|
||||
- Support email uniquement
|
||||
|
||||
### Plans payants Resend
|
||||
- À partir de 20$/mois pour 50 000 emails
|
||||
- Support prioritaire
|
||||
- Webhooks et analytics avancés
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Les emails n'arrivent pas
|
||||
|
||||
1. Vérifiez que `RESEND_API_KEY` est correctement configurée
|
||||
2. Vérifiez que `RESEND_FROM_EMAIL` utilise un domaine vérifié
|
||||
3. Consultez les logs du serveur pour voir les erreurs
|
||||
4. Vérifiez le dossier spam/courrier indésirable
|
||||
5. Consultez le tableau de bord Resend pour voir l'état des envois
|
||||
|
||||
### Erreur "Domain not verified"
|
||||
|
||||
Vous devez vérifier votre domaine dans Resend en ajoutant les enregistrements DNS requis.
|
||||
|
||||
### Quota dépassé
|
||||
|
||||
Si vous dépassez le quota gratuit, vous devrez :
|
||||
- Attendre le lendemain (quota journalier)
|
||||
- Passer à un plan payant
|
||||
- Utiliser un autre service d'envoi
|
||||
|
||||
## Support
|
||||
|
||||
Pour toute question sur la configuration de l'envoi d'emails, consultez :
|
||||
- [Documentation Resend](https://resend.com/docs)
|
||||
- [API Reference Resend](https://resend.com/docs/api-reference)
|
||||
145
server/_core/emailSender.ts
Normal file
145
server/_core/emailSender.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Service d'envoi d'emails réels via Resend API
|
||||
*
|
||||
* Configuration requise:
|
||||
* - RESEND_API_KEY: Clé API Resend (obtenir sur https://resend.com)
|
||||
* - RESEND_FROM_EMAIL: Adresse email d'envoi (doit être vérifiée dans Resend)
|
||||
*
|
||||
* Si ces variables ne sont pas configurées, les emails seront simulés
|
||||
* et envoyés comme notifications au propriétaire du projet.
|
||||
*/
|
||||
|
||||
import { ENV } from "./env";
|
||||
import { notifyOwner } from "./notification";
|
||||
|
||||
interface EmailParams {
|
||||
to: string;
|
||||
subject: string;
|
||||
html: string;
|
||||
attachments?: Array<{
|
||||
filename: string;
|
||||
content: string;
|
||||
contentType: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface ResendEmailRequest {
|
||||
from: string;
|
||||
to: string[];
|
||||
subject: string;
|
||||
html: string;
|
||||
attachments?: Array<{
|
||||
filename: string;
|
||||
content: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un email via Resend API
|
||||
*/
|
||||
async function sendViaResend(params: EmailParams): Promise<boolean> {
|
||||
const resendApiKey = process.env.RESEND_API_KEY;
|
||||
const fromEmail = process.env.RESEND_FROM_EMAIL || 'noreply@manusvm.computer';
|
||||
|
||||
if (!resendApiKey) {
|
||||
console.warn('[Email] RESEND_API_KEY non configurée, email simulé');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload: ResendEmailRequest = {
|
||||
from: fromEmail,
|
||||
to: [params.to],
|
||||
subject: params.subject,
|
||||
html: params.html,
|
||||
};
|
||||
|
||||
// Ajouter les pièces jointes si présentes
|
||||
if (params.attachments && params.attachments.length > 0) {
|
||||
payload.attachments = params.attachments.map(att => ({
|
||||
filename: att.filename,
|
||||
content: att.content, // Resend accepte base64 ou string
|
||||
}));
|
||||
}
|
||||
|
||||
const response = await fetch('https://api.resend.com/emails', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${resendApiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
console.error('[Email] Erreur Resend:', response.status, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[Email] Email envoyé via Resend:', result.id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[Email] Exception lors de l\'envoi via Resend:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simule l'envoi d'un email en créant une notification pour le propriétaire
|
||||
*/
|
||||
async function simulateEmail(params: EmailParams): Promise<boolean> {
|
||||
console.log('=== EMAIL SIMULÉ ===');
|
||||
console.log('To:', params.to);
|
||||
console.log('Subject:', params.subject);
|
||||
|
||||
const emailContent = `
|
||||
Destinataire: ${params.to}
|
||||
Sujet: ${params.subject}
|
||||
|
||||
${params.html.replace(/<[^>]*>/g, '').substring(0, 500)}...
|
||||
|
||||
${params.attachments ? `Pièces jointes: ${params.attachments.map(a => a.filename).join(', ')}` : ''}
|
||||
|
||||
⚠️ Cet email est simulé. Pour envoyer de vrais emails:
|
||||
1. Créez un compte sur https://resend.com
|
||||
2. Ajoutez RESEND_API_KEY dans les secrets du projet
|
||||
3. Ajoutez RESEND_FROM_EMAIL (ex: noreply@votredomaine.com)`;
|
||||
|
||||
try {
|
||||
await notifyOwner({
|
||||
title: `📧 Email simulé: ${params.subject}`,
|
||||
content: emailContent,
|
||||
});
|
||||
console.log('✓ Email simulé (notification envoyée au propriétaire)');
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la simulation d\'email:', error);
|
||||
}
|
||||
|
||||
console.log('===================');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un email (réel ou simulé selon la configuration)
|
||||
*/
|
||||
export async function sendEmail(params: EmailParams): Promise<boolean> {
|
||||
// Tenter d'envoyer via Resend
|
||||
const sent = await sendViaResend(params);
|
||||
|
||||
// Si l'envoi échoue (pas de clé API ou erreur), simuler
|
||||
if (!sent) {
|
||||
return await simulateEmail(params);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le service d'envoi d'emails réel est configuré
|
||||
*/
|
||||
export function isEmailServiceConfigured(): boolean {
|
||||
return !!process.env.RESEND_API_KEY;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { generateFormationICS } from "./icsGenerator";
|
||||
import { notifyOwner } from "./_core/notification";
|
||||
import { sendEmail as sendEmailViaService, isEmailServiceConfigured } from "./_core/emailSender";
|
||||
|
||||
interface EmailParams {
|
||||
to: string;
|
||||
@@ -18,36 +18,11 @@ interface EmailParams {
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un email en utilisant le système de notification Manus
|
||||
* Note: Pour un envoi SMTP réel, intégrer un service comme SendGrid ou Mailgun
|
||||
* 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> {
|
||||
console.log('=== EMAIL ENVOYÉ ===');
|
||||
console.log('To:', params.to);
|
||||
console.log('Subject:', params.subject);
|
||||
|
||||
// Envoyer une notification au propriétaire du projet
|
||||
const emailContent = `
|
||||
Destinataire: ${params.to}
|
||||
Sujet: ${params.subject}
|
||||
|
||||
${params.html.replace(/<[^>]*>/g, '').substring(0, 500)}...
|
||||
|
||||
${params.attachments ? `Pièces jointes: ${params.attachments.map(a => a.filename).join(', ')}` : ''}`;
|
||||
|
||||
try {
|
||||
await notifyOwner({
|
||||
title: `Email envoyé: ${params.subject}`,
|
||||
content: emailContent,
|
||||
});
|
||||
console.log('✓ Email traité avec succès');
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de l\'envoi de la notification:', error);
|
||||
}
|
||||
|
||||
console.log('===================');
|
||||
|
||||
return true;
|
||||
return await sendEmailViaService(params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
7
todo.md
7
todo.md
@@ -209,3 +209,10 @@
|
||||
|
||||
- [x] Intégrer la page AdminUsers dans le DashboardLayout
|
||||
- [x] Vérifier l'affichage du menu latéral sur la page utilisateurs
|
||||
|
||||
## Correction de l'envoi d'emails pour réinitialisation de mot de passe
|
||||
|
||||
- [x] Analyser le service d'envoi d'emails actuel
|
||||
- [x] Rechercher l'API d'envoi d'emails disponible dans Manus
|
||||
- [x] Intégrer un vrai service d'envoi d'emails (Resend)
|
||||
- [x] Créer la documentation de configuration EMAIL_SETUP.md
|
||||
|
||||
Reference in New Issue
Block a user