""" 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 = """
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
""" 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) }