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
|