# app/services/mailer.py import smtplib import ssl from email.message import EmailMessage from typing import Optional from loguru import logger from app.core.config import get_app_settings def _build_message(*, from_email: str, to_email: str, subject: str, html: str) -> EmailMessage: msg = EmailMessage() msg["From"] = from_email msg["To"] = to_email msg["Subject"] = subject msg.set_content("Your email client does not support HTML.") msg.add_alternative(html, subtype="html") return msg def send_email(to_email: str, subject: str, html: str) -> bool: """ 同步发送;成功返回 True,失败返回 False,并打印详细日志。 - 端口 465:使用 SMTP_SSL - 其他端口:使用 SMTP + (可选)STARTTLS """ s = get_app_settings() from_email = str(s.mail_from) smtp_host = s.smtp_host smtp_port = int(s.smtp_port) smtp_user: Optional[str] = s.smtp_user.get_secret_value() if s.smtp_user else None smtp_pass: Optional[str] = s.smtp_password.get_secret_value() if s.smtp_password else None msg = _build_message(from_email=from_email, to_email=to_email, subject=subject, html=html) logger.info( "SMTP send start → host={} port={} tls={} from={} to={}", smtp_host, smtp_port, s.smtp_tls, from_email, to_email, ) try: if smtp_port == 465: context = ssl.create_default_context() with smtplib.SMTP_SSL(smtp_host, smtp_port, context=context, timeout=20) as server: if smtp_user and smtp_pass: server.login(smtp_user, smtp_pass) server.send_message(msg) else: with smtplib.SMTP(smtp_host, smtp_port, timeout=20) as server: server.ehlo() if s.smtp_tls: context = ssl.create_default_context() server.starttls(context=context) server.ehlo() if smtp_user and smtp_pass: server.login(smtp_user, smtp_pass) server.send_message(msg) logger.info("SMTP send OK to {}", to_email) return True except smtplib.SMTPResponseException as e: # 能拿到服务端 code/resp 的错误 logger.error("SMTPResponseException: code={} msg={}", getattr(e, "smtp_code", None), getattr(e, "smtp_error", None)) return False except Exception as e: logger.exception("SMTP send failed: {}", e) return False