# app/services/mailer.py import smtplib import ssl from email.message import EmailMessage from app.core.config import get_app_settings def send_email(to_email: str, subject: str, html: str) -> None: """ 发送邮件: - 端口为 465 时使用 SSL 直连(SMTP_SSL) - 其他端口:按配置 smtp_tls 决定是否 STARTTLS """ s = get_app_settings() host = s.smtp_host port = int(s.smtp_port) use_starttls = bool(s.smtp_tls) # SecretStr / Optional 兼容处理 smtp_user = s.smtp_user.get_secret_value() if getattr(s, "smtp_user", None) else None smtp_pass = s.smtp_password.get_secret_value() if getattr(s, "smtp_password", None) else None msg = EmailMessage() msg["From"] = str(s.mail_from) # EmailStr -> str msg["To"] = to_email msg["Subject"] = subject # 纯文本 + HTML(多部件) msg.set_content("Your mail client does not support HTML.") msg.add_alternative(html, subtype="html") ctx = ssl.create_default_context() # === 建立连接 === if port == 465: server = smtplib.SMTP_SSL(host, port, context=ctx, timeout=20) else: server = smtplib.SMTP(host, port, timeout=20) server.ehlo() if use_starttls: server.starttls(context=ctx) server.ehlo() try: # 如配置了用户名/密码,则登录 if smtp_user and smtp_pass: server.login(smtp_user, smtp_pass) server.send_message(msg) finally: try: server.quit() except Exception: # 连接已断开也无妨,忽略 pass