69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
# 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
|