""" Async SMTP Email Service with retry logic and proper error handling """ import aiosmtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import os import logging from typing import Optional import asyncio logger = logging.getLogger(__name__) class EmailService: """Async SMTP client for sending authentication codes""" def __init__(self): self.smtp_host = os.getenv("SMTP_HOST", "mail.romfast.ro") self.smtp_port = int(os.getenv("SMTP_PORT", "587")) self.smtp_user = os.getenv("SMTP_USER") self.smtp_password = os.getenv("SMTP_PASSWORD") self.from_email = os.getenv("SMTP_FROM_EMAIL") self.from_name = os.getenv("SMTP_FROM_NAME", "ROA2WEB") self.use_tls = os.getenv("SMTP_USE_TLS", "true").lower() == "true" # Retry configuration self.max_retries = int(os.getenv("EMAIL_MAX_RETRIES", "3")) self.retry_delay = float(os.getenv("EMAIL_RETRY_DELAY", "2.0")) # seconds # Validate required config if not all([self.smtp_user, self.smtp_password, self.from_email]): raise ValueError("SMTP configuration incomplete. Check .env file.") async def send_auth_code( self, to_email: str, code: str, username: str ) -> bool: """ Send authentication code via email with retry logic Args: to_email: Recipient email address code: 6-digit authentication code username: Oracle username for personalization Returns: True if email sent successfully (after retries if needed) Raises: No exceptions - returns False on all failures """ subject = "Codul tău de autentificare ROA2WEB" html_body = self._create_email_template(code, username) for attempt in range(1, self.max_retries + 1): try: await self._send_email(to_email, subject, html_body) logger.info( f"Email sent successfully to {to_email} " f"(attempt {attempt}/{self.max_retries})" ) return True except aiosmtplib.SMTPException as e: logger.warning( f"SMTP error on attempt {attempt}/{self.max_retries}: {e}" ) if attempt < self.max_retries: # Exponential backoff: 2s, 4s, 8s delay = self.retry_delay * (2 ** (attempt - 1)) logger.info(f"Retrying in {delay}s...") await asyncio.sleep(delay) else: logger.error(f"Failed to send email to {to_email} after {self.max_retries} attempts") except Exception as e: logger.error(f"Unexpected error sending email: {e}", exc_info=True) return False return False async def _send_email( self, to_email: str, subject: str, html_body: str ) -> None: """ Internal async SMTP sender Raises: aiosmtplib.SMTPException: On SMTP errors """ message = MIMEMultipart("alternative") message["From"] = f"{self.from_name} <{self.from_email}>" message["To"] = to_email message["Subject"] = subject # Attach HTML body html_part = MIMEText(html_body, "html", "utf-8") message.attach(html_part) # Send via async SMTP with STARTTLS # Using start_tls parameter for automatic STARTTLS handling smtp = aiosmtplib.SMTP( hostname=self.smtp_host, port=self.smtp_port, start_tls=self.use_tls, # Use start_tls instead of use_tls timeout=30 ) try: await smtp.connect() await smtp.login(self.smtp_user, self.smtp_password) await smtp.send_message(message) finally: try: await smtp.quit() except: pass def _create_email_template(self, code: str, username: str) -> str: """Generate HTML email template""" return f"""

ROA2WEB

Autentificare Telegram Bot

Salut {username},

Ai solicitat autentificarea în aplicația ROA2WEB Telegram Bot.

Codul tău de autentificare:

{code}

Introdu acest cod în conversația Telegram

Important: Acest cod expiră în 5 minute și poate fi folosit o singură dată.

După introducerea codului, vei fi solicitat să introduci parola ta Oracle.


Nu ai solicitat acest cod?
Dacă nu ai inițiat această autentificare, poți ignora acest email în siguranță. Nimeni nu va avea acces la contul tău fără parola ta Oracle.

""" # Singleton instance _email_service: Optional[EmailService] = None def get_email_service() -> EmailService: """Get or create singleton email service instance""" global _email_service if _email_service is None: _email_service = EmailService() return _email_service