import email from config.settings import MAIL, MAIL_PASSWORD import smtplib from email.message import EmailMessage from logging import getLogger from typing import Optional from services.logging_service import structured_logger, LogLevel logger = getLogger(__name__) class EmailSender: def __init__(self): self.email = MAIL self.password = MAIL_PASSWORD self._smtp: Optional[smtplib.SMTP_SSL] = None self.connect() def connect(self): if self._smtp is None: logger.info("Establishing new SMTP connection...") structured_logger.log_email_event( "Establishing SMTP connection", LogLevel.INFO, {"email_server": "smtp.gmail.com", "port": 465, "sender_email": self.email} ) try: self._smtp = smtplib.SMTP_SSL('smtp.gmail.com', 465) self._smtp.login(self.email, self.password) self._smtp.ehlo() logger.info("SMTP connection established and authenticated.") structured_logger.log_email_event( "SMTP authentication successful", LogLevel.INFO, {"sender_email": self.email} ) except smtplib.SMTPAuthenticationError as e: error_msg = f"SMTP authentication failed: {e}" logger.error(error_msg) structured_logger.log_email_event( "SMTP authentication failed", LogLevel.ERROR, { "sender_email": self.email, "error": str(e), "error_type": "SMTPAuthenticationError" } ) raise except Exception as e: error_msg = f"Failed to establish SMTP connection: {e}" logger.error(error_msg) structured_logger.log_email_event( "SMTP connection failed", LogLevel.ERROR, { "sender_email": self.email, "error": str(e), "error_type": type(e).__name__ } ) raise def close(self): if self._smtp: logger.info("Closing SMTP connection.") structured_logger.log_email_event( "Closing SMTP connection", LogLevel.INFO, {"sender_email": self.email} ) try: self._smtp.quit() self._smtp = None except Exception as e: logger.warning(f"Error closing SMTP connection: {e}") self._smtp = None # Force reset even if quit fails def send_email(self, subject: str, body: str, to: list[str], **kwargs): if self._smtp is None: self.connect() logger.debug(f"Preparing to send email to: {to} with subject: '{subject}'") structured_logger.log_email_event( f"Preparing to send email with subject: '{subject}'", LogLevel.INFO, { "subject": subject, "recipients": to, "sender_email": self.email, "body_length": len(body), "kwargs_count": len(kwargs) } ) try: msg = EmailMessage() msg['Subject'] = subject msg['From'] = self.email msg['To'] = ", ".join(to) msg.set_content('Este correo tiene contenido HTML.') msg.add_alternative(body.format(**kwargs), subtype='html') if not self._smtp: error_msg = "Cannot send email because SMTP connection is not established" logger.error(error_msg) structured_logger.log_email_event( "Email send failed: SMTP connection not established", LogLevel.ERROR, { "subject": subject, "recipients": to, "sender_email": self.email } ) raise ConnectionError(error_msg) self._smtp.send_message(msg) logger.info(f"Email sent to {to} with subject '{subject}'.") structured_logger.log_email_event( f"Email sent successfully", LogLevel.INFO, { "subject": subject, "recipients": to, "sender_email": self.email, "recipients_count": len(to) } ) except smtplib.SMTPServerDisconnected as e: logger.warning("SMTP connection disconnected, retrying...") structured_logger.log_email_event( "SMTP disconnected during send, attempting retry", LogLevel.WARNING, { "subject": subject, "recipients": to, "error": str(e) } ) try: self.close() self.connect() self._smtp.send_message(msg) logger.info(f"Email resent successfully to {to}.") structured_logger.log_email_event( "Email sent successfully after retry", LogLevel.INFO, { "subject": subject, "recipients": to, "sender_email": self.email, "retry_attempt": True } ) except Exception as retry_error: error_msg = f"Failed to resend email after retry: {retry_error}" logger.error(error_msg) structured_logger.log_email_event( "Email retry failed", LogLevel.ERROR, { "subject": subject, "recipients": to, "original_error": str(e), "retry_error": str(retry_error), "error_type": type(retry_error).__name__ } ) self.close() raise except Exception as e: error_msg = f"Failed to send email to {to}: {e}" logger.error(error_msg) structured_logger.log_email_event( "Email send failed", LogLevel.ERROR, { "subject": subject, "recipients": to, "sender_email": self.email, "error": str(e), "error_type": type(e).__name__ } ) self.close() raise email_sender: Optional[EmailSender] = None def initialize_email_sender(): global email_sender email_sender = EmailSender() def get_email_sender() -> EmailSender: if email_sender is None: initialize_email_sender() if not email_sender: raise ValueError("Email sender is not initialized and failed to initialize.") return email_sender