| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- 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
|