"""Email client for sending files via Gmail SMTP.""" from __future__ import annotations import logging import smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from pathlib import Path log = logging.getLogger(__name__) class EmailClient: """Send emails with attachments via SMTP_SSL (Gmail app-password friendly).""" def __init__(self, smtp_host: str, smtp_port: int, username: str, password: str): self.smtp_host = smtp_host self.smtp_port = smtp_port self.username = username self.password = password def send( self, to: str, subject: str, body: str, attachments: list[Path] | None = None, ) -> None: """Send an email, optionally with file attachments. Raises smtplib.SMTPException on failure. """ msg = MIMEMultipart() msg["From"] = self.username msg["To"] = to msg["Subject"] = subject msg.attach(MIMEText(body, "plain")) for file_path in attachments or []: part = MIMEBase("application", "octet-stream") part.set_payload(file_path.read_bytes()) encoders.encode_base64(part) part.add_header( "Content-Disposition", f"attachment; filename={file_path.name}", ) msg.attach(part) log.info("Sending email to %s — subject: %s", to, subject) with smtplib.SMTP_SSL(self.smtp_host, self.smtp_port) as server: server.login(self.username, self.password) server.send_message(msg) log.info("Email sent successfully to %s", to)