112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
|
|
"""邮件发送工具。"""
|
|||
|
|
import smtplib
|
|||
|
|
import socket
|
|||
|
|
from email.message import EmailMessage
|
|||
|
|
from html import escape
|
|||
|
|
|
|||
|
|
from insurance.config import get_config
|
|||
|
|
|
|||
|
|
|
|||
|
|
class EmailSendError(Exception):
|
|||
|
|
"""邮件发送失败,携带可展示给用户的错误信息。"""
|
|||
|
|
|
|||
|
|
def __init__(self, user_message: str):
|
|||
|
|
super().__init__(user_message)
|
|||
|
|
self.user_message = user_message
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _get_mail_config(key: str, default=None):
|
|||
|
|
value = get_config(key, "")
|
|||
|
|
if value != "":
|
|||
|
|
return value
|
|||
|
|
try:
|
|||
|
|
from configs import dify_config
|
|||
|
|
value = getattr(dify_config, key, None)
|
|||
|
|
return default if value is None else value
|
|||
|
|
except Exception:
|
|||
|
|
return default
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _as_bool(value) -> bool:
|
|||
|
|
if isinstance(value, bool):
|
|||
|
|
return value
|
|||
|
|
return str(value or "").strip().lower() in ("true", "1", "yes", "on")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _send_with_dify_mail(to_email: str, subject: str, content: str) -> bool:
|
|||
|
|
"""优先复用 Dify 已初始化的邮件客户端,兼容 smtp/resend/sendgrid。"""
|
|||
|
|
try:
|
|||
|
|
from extensions.ext_mail import mail
|
|||
|
|
except Exception:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
if not getattr(mail, "is_inited", lambda: False)():
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
html = "<pre style=\"font-family: sans-serif; white-space: pre-wrap;\">" + escape(content) + "</pre>"
|
|||
|
|
try:
|
|||
|
|
mail.send(to=to_email, subject=subject, html=html)
|
|||
|
|
return True
|
|||
|
|
except Exception as exc:
|
|||
|
|
raise EmailSendError("Dify 邮件服务发送失败,请检查邮件服务配置") from exc
|
|||
|
|
|
|||
|
|
|
|||
|
|
def send_email(to_email: str, subject: str, content: str) -> bool:
|
|||
|
|
"""通过 Dify 风格的 SMTP 配置发送纯文本邮件。"""
|
|||
|
|
if _send_with_dify_mail(to_email, subject, content):
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
mail_type = str(_get_mail_config("MAIL_TYPE", "") or "").strip().lower()
|
|||
|
|
host = _get_mail_config("SMTP_SERVER", "")
|
|||
|
|
if mail_type and mail_type != "smtp":
|
|||
|
|
raise EmailSendError("当前邮件服务未初始化,请检查 Dify 邮件服务配置")
|
|||
|
|
if not mail_type and not host:
|
|||
|
|
raise EmailSendError("邮件服务未配置,请联系管理员")
|
|||
|
|
|
|||
|
|
port = int(_get_mail_config("SMTP_PORT", "587"))
|
|||
|
|
username = _get_mail_config("SMTP_USERNAME", "")
|
|||
|
|
password = _get_mail_config("SMTP_PASSWORD", "")
|
|||
|
|
default_send_from = _get_mail_config("MAIL_DEFAULT_SEND_FROM", "")
|
|||
|
|
from_email = default_send_from
|
|||
|
|
|
|||
|
|
# Dify 语义:SMTP_USE_TLS=true 且 SMTP_OPPORTUNISTIC_TLS=false 表示 SMTP_SSL;
|
|||
|
|
# SMTP_USE_TLS=true 且 SMTP_OPPORTUNISTIC_TLS=true 表示 STARTTLS。
|
|||
|
|
use_tls = _as_bool(_get_mail_config("SMTP_USE_TLS", "false"))
|
|||
|
|
opportunistic_tls = _as_bool(_get_mail_config("SMTP_OPPORTUNISTIC_TLS", "false"))
|
|||
|
|
use_starttls = use_tls and opportunistic_tls
|
|||
|
|
use_ssl = use_tls and not use_starttls
|
|||
|
|
|
|||
|
|
placeholders = {"smtp.example.com", "smtp.gmail.com", "your_smtp_password", "no-reply@example.com"}
|
|||
|
|
if not host or not from_email:
|
|||
|
|
raise EmailSendError("邮件服务未配置,请联系管理员")
|
|||
|
|
if host in placeholders or password in placeholders or any(value in from_email for value in placeholders):
|
|||
|
|
raise EmailSendError("邮件服务仍是示例配置,请联系管理员")
|
|||
|
|
|
|||
|
|
message = EmailMessage()
|
|||
|
|
message["Subject"] = subject
|
|||
|
|
message["From"] = from_email
|
|||
|
|
message["To"] = to_email
|
|||
|
|
message.set_content(content)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
if use_ssl:
|
|||
|
|
with smtplib.SMTP_SSL(host, port, timeout=10) as server:
|
|||
|
|
if username and password:
|
|||
|
|
server.login(username, password)
|
|||
|
|
server.send_message(message)
|
|||
|
|
else:
|
|||
|
|
with smtplib.SMTP(host, port, timeout=10) as server:
|
|||
|
|
if use_starttls:
|
|||
|
|
server.starttls()
|
|||
|
|
if username and password:
|
|||
|
|
server.login(username, password)
|
|||
|
|
server.send_message(message)
|
|||
|
|
except smtplib.SMTPAuthenticationError as exc:
|
|||
|
|
raise EmailSendError("SMTP 认证失败,请检查邮箱账号或授权码") from exc
|
|||
|
|
except (smtplib.SMTPConnectError, socket.timeout, TimeoutError, OSError) as exc:
|
|||
|
|
raise EmailSendError("SMTP 连接失败,请检查服务器地址、端口和网络") from exc
|
|||
|
|
except smtplib.SMTPException as exc:
|
|||
|
|
raise EmailSendError("SMTP 发送失败,请检查邮箱服务配置") from exc
|
|||
|
|
|
|||
|
|
return True
|