"""邮件发送工具(基于标准库 smtplib,465 端口 SSL)。 配置来自 app.config.setting.settings(SMTP_HOST/PORT/USER/PASSWORD/FROM/FROM_NAME/TLS)。 """ from email.header import Header from email.mime.text import MIMEText from smtplib import SMTP_SSL, SMTPException from app.config.setting import settings from app.core.logger import logger def send_email(to_addr: str, subject: str, html_body: str) -> None: """发送一封 HTML 邮件(同步,内部阻塞;服务层用线程池调用)。 参数: - to_addr (str): 收件人邮箱 - subject (str): 邮件主题 - html_body (str): HTML 正文 异常: - SMTPException: 发送失败时抛出,由调用方捕获并处理 """ if not settings.SMTP_HOST or not settings.SMTP_USER: raise RuntimeError("SMTP 未配置:请在 .env 中设置 SMTP_HOST/SMTP_USER/SMTP_PASSWORD") from_addr = settings.SMTP_FROM or settings.SMTP_USER display = settings.SMTP_FROM_NAME or from_addr # 显示名单独编码,邮箱地址保持 ASCII from_header = f"{Header(display, 'utf-8').encode()} <{from_addr}>" msg = MIMEText(html_body, "html", "utf-8") msg["From"] = from_header msg["To"] = to_addr msg["Subject"] = Header(subject, "utf-8").encode() with SMTP_SSL(settings.SMTP_HOST, settings.SMTP_PORT, timeout=10) as server: server.login(settings.SMTP_USER, settings.SMTP_PASSWORD) server.sendmail(from_addr, [to_addr], msg.as_string()) def send_reset_code_email(to_addr: str, code: str, expire_seconds: int) -> None: """发送找回密码验证码邮件。""" try: send_email( to_addr=to_addr, subject="【dpb】找回密码验证码", html_body=( f"
您好,
" f"您正在申请找回密码,验证码为:
" f'{code}
' f"验证码 {expire_seconds // 60} 分钟内有效,请勿泄露给他人。
" f"若非本人操作,请忽略本邮件。
" ), ) except SMTPException as e: logger.error(f"发送验证码邮件失败 -> {to_addr}: {e!s}") raise except Exception as e: # noqa: BLE001 logger.error(f"发送验证码邮件异常 -> {to_addr}: {e!s}") raise