"""Unified email sending service. All email sending (system emails, alert notifications) goes through this module. Supports TLS, STARTTLS, and plain SMTP. Handles Transit + legacy Fernet password decryption. """ import logging from email.message import EmailMessage from typing import Optional import aiosmtplib logger = logging.getLogger(__name__) class SMTPConfig: """SMTP connection configuration.""" def __init__( self, host: str, port: int = 587, user: Optional[str] = None, password: Optional[str] = None, use_tls: bool = False, from_address: str = "noreply@example.com", ): self.host = host self.port = port self.user = user self.password = password self.use_tls = use_tls self.from_address = from_address async def send_email( to: str, subject: str, html: str, plain_text: str, smtp_config: SMTPConfig, ) -> None: """Send an email via SMTP. Args: to: Recipient email address. subject: Email subject line. html: HTML body. plain_text: Plain text fallback body. smtp_config: SMTP connection settings. Raises: aiosmtplib.SMTPException: On SMTP connection or send failure. """ msg = EmailMessage() msg["Subject"] = subject msg["From"] = smtp_config.from_address msg["To"] = to msg.set_content(plain_text) msg.add_alternative(html, subtype="html") use_tls = smtp_config.use_tls start_tls = not use_tls if smtp_config.port != 25 else False await aiosmtplib.send( msg, hostname=smtp_config.host, port=smtp_config.port, username=smtp_config.user or None, password=smtp_config.password or None, use_tls=use_tls, start_tls=start_tls, ) async def test_smtp_connection(smtp_config: SMTPConfig) -> dict: """Test SMTP connectivity without sending an email. Returns: dict with "success" bool and "message" string. """ try: smtp = aiosmtplib.SMTP( hostname=smtp_config.host, port=smtp_config.port, use_tls=smtp_config.use_tls, start_tls=not smtp_config.use_tls if smtp_config.port != 25 else False, ) await smtp.connect() if smtp_config.user and smtp_config.password: await smtp.login(smtp_config.user, smtp_config.password) await smtp.quit() return {"success": True, "message": "SMTP connection successful"} except Exception as e: return {"success": False, "message": str(e)} async def send_test_email(to: str, smtp_config: SMTPConfig) -> dict: """Send a test email to verify the full SMTP flow. Returns: dict with "success" bool and "message" string. """ html = """

TOD — Email Test

This is a test email from The Other Dude.

If you're reading this, your SMTP configuration is working correctly.

Sent from TOD Fleet Management

""" plain = "TOD — Email Test\n\nThis is a test email from The Other Dude.\nIf you're reading this, your SMTP configuration is working correctly." try: await send_email(to, "TOD — Test Email", html, plain, smtp_config) return {"success": True, "message": f"Test email sent to {to}"} except Exception as e: return {"success": False, "message": str(e)}