import smtplib from contextlib import contextmanager from email.message import EmailMessage from loguru import logger from ..config.email import EMAIL_CONFIG class SMTPError(Exception): pass @contextmanager def smtp_connection(): """Context manager that yields a connected, authenticated SMTP session.""" server = smtplib.SMTP(EMAIL_CONFIG.smtp_host, EMAIL_CONFIG.smtp_port) try: server.starttls() server.login(EMAIL_CONFIG.smtp_key, EMAIL_CONFIG.smtp_secret) yield server finally: server.quit() def send_email(email: EmailMessage) -> None: try: with smtp_connection() as server: server.send_message(email) except smtplib.SMTPException as e: logger.error(f"Failed to send email: {e}") raise SMTPError(f"SMTP error: {e}") from e def send_batch(emails: list[EmailMessage]) -> int: """Send multiple emails over a single SMTP connection.""" if not emails: return 0 sent = 0 try: with smtp_connection() as server: for email in emails: try: server.send_message(email) sent += 1 except smtplib.SMTPException as e: logger.error(f"Failed to send email: {e}") except smtplib.SMTPException as e: logger.error(f"Failed to establish SMTP connection: {e}") return sent def send_emails(emails: list[EmailMessage]) -> int: """Send multiple emails (uses batch connection reuse).""" return send_batch(emails) def chunk_recipients(recipients: list[str], max_per_chunk: int | None = None) -> list[list[str]]: max_size = max_per_chunk or EMAIL_CONFIG.max_recipients_per_email return [recipients[i:i + max_size] for i in range(0, len(recipients), max_size)]