Files
notytex/services/email_service.py

214 lines
8.4 KiB
Python

"""
Service d'envoi d'emails pour Notytex.
Gère la configuration SMTP et l'envoi de bilans d'évaluation.
"""
import smtplib
import logging
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import List, Optional, Dict, Any
from flask import current_app
from premailer import transform
class EmailService:
"""Service d'envoi d'emails avec configuration dynamique."""
def __init__(self, config_manager=None):
"""Initialise le service avec le gestionnaire de configuration."""
if config_manager is None:
from app_config import config_manager as default_manager
self.config_manager = default_manager
else:
self.config_manager = config_manager
self.logger = logging.getLogger(__name__)
def get_smtp_config(self) -> Dict[str, Any]:
"""Récupère la configuration SMTP depuis la base de données."""
try:
return {
'host': self.config_manager.get('email.smtp_host', ''),
'port': int(self.config_manager.get('email.smtp_port', 587)),
'username': self.config_manager.get('email.username', ''),
'password': self.config_manager.get('email.password', ''),
'use_tls': self.config_manager.get('email.use_tls', 'true').lower() == 'true',
'from_name': self.config_manager.get('email.from_name', 'Notytex'),
'from_address': self.config_manager.get('email.from_address', ''),
}
except Exception as e:
self.logger.error(f"Erreur lors de la récupération de la configuration email: {e}")
return {}
def is_configured(self) -> bool:
"""Vérifie si la configuration email est complète."""
config = self.get_smtp_config()
# Vérifier les champs obligatoires de base
if not config.get('host'):
self.logger.warning("Configuration email incomplète: champ 'host' manquant")
return False
if not config.get('from_address'):
self.logger.warning("Configuration email incomplète: champ 'from_address' manquant")
return False
# Pour les serveurs locaux de test (localhost), l'authentification n'est pas requise
is_localhost = config.get('host', '').lower() in ['localhost', '127.0.0.1']
is_test_port = str(config.get('port', '')).strip() in ['1025', '2525', '8025']
if not is_localhost or not is_test_port:
# Pour les vrais serveurs SMTP, username et password sont requis
if not config.get('username'):
self.logger.warning("Configuration email incomplète: champ 'username' manquant")
return False
if not config.get('password'):
self.logger.warning("Configuration email incomplète: champ 'password' manquant")
return False
return True
def send_email(self, to_emails: List[str], subject: str, html_body: str,
text_body: Optional[str] = None) -> Dict[str, Any]:
"""
Envoie un email à une liste de destinataires.
Args:
to_emails: Liste des adresses email destinataires
subject: Sujet de l'email
html_body: Corps de l'email en HTML
text_body: Corps de l'email en texte brut (optionnel)
Returns:
Dict avec le statut de l'envoi et les détails
"""
if not self.is_configured():
return {
'success': False,
'error': 'Configuration email incomplète. Vérifiez les paramètres dans Configuration > Email.'
}
config = self.get_smtp_config()
try:
# Préparer l'email
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = f"{config['from_name']} <{config['from_address']}>"
msg['To'] = ', '.join(to_emails)
# Ajouter le corps en texte brut si fourni
if text_body:
part1 = MIMEText(text_body, 'plain', 'utf-8')
msg.attach(part1)
# Transformer le HTML pour optimiser l'affichage email
optimized_html = transform(html_body)
part2 = MIMEText(optimized_html, 'html', 'utf-8')
msg.attach(part2)
# Connexion SMTP et envoi
with smtplib.SMTP(config['host'], config['port']) as server:
if config['use_tls']:
server.starttls()
# Ne pas s'authentifier sur les serveurs de test locaux
is_localhost = config.get('host', '').lower() in ['localhost', '127.0.0.1']
is_test_port = str(config.get('port', '')).strip() in ['1025', '2525', '8025']
if not (is_localhost and is_test_port) and config.get('username') and config.get('password'):
server.login(config['username'], config['password'])
server.send_message(msg)
self.logger.info(f"Email envoyé avec succès à {len(to_emails)} destinataires: {subject}")
return {
'success': True,
'message': f'Email envoyé avec succès à {len(to_emails)} destinataire(s)',
'recipients_count': len(to_emails)
}
except smtplib.SMTPAuthenticationError as e:
error_msg = "Erreur d'authentification SMTP. Vérifiez les identifiants."
self.logger.error(f"Erreur SMTP Auth: {e}")
return {'success': False, 'error': error_msg}
except smtplib.SMTPException as e:
error_msg = f"Erreur SMTP lors de l'envoi: {str(e)}"
self.logger.error(f"Erreur SMTP: {e}")
return {'success': False, 'error': error_msg}
except Exception as e:
error_msg = f"Erreur inattendue lors de l'envoi: {str(e)}"
self.logger.error(f"Erreur envoi email: {e}")
return {'success': False, 'error': error_msg}
def send_test_email(self, to_email: str) -> Dict[str, Any]:
"""
Envoie un email de test pour vérifier la configuration.
Args:
to_email: Adresse email de test
Returns:
Dict avec le statut du test
"""
subject = "Test de configuration email - Notytex"
html_body = """
<html>
<body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #3b82f6;">Test de configuration email</h2>
<p>Félicitations ! Votre configuration email fonctionne correctement.</p>
<p>Vous pouvez maintenant envoyer des bilans d'évaluation par email.</p>
<hr style="margin: 20px 0;">
<p style="color: #6b7280; font-size: 12px;">
Email envoyé depuis Notytex - Système de gestion scolaire
</p>
</body>
</html>
"""
text_body = """
Test de configuration email - Notytex
Félicitations ! Votre configuration email fonctionne correctement.
Vous pouvez maintenant envoyer des bilans d'évaluation par email.
---
Email envoyé depuis Notytex - Système de gestion scolaire
"""
return self.send_email([to_email], subject, html_body, text_body)
def validate_email_addresses(self, emails: List[str]) -> Dict[str, Any]:
"""
Valide une liste d'adresses email.
Args:
emails: Liste des adresses à valider
Returns:
Dict avec les emails valides et invalides
"""
import re
email_regex = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
valid_emails = []
invalid_emails = []
for email in emails:
email = email.strip()
if email and email_regex.match(email):
valid_emails.append(email)
elif email: # Email non vide mais invalide
invalid_emails.append(email)
return {
'valid': valid_emails,
'invalid': invalid_emails,
'valid_count': len(valid_emails),
'invalid_count': len(invalid_emails)
}