Files
dify/api/extensions/ext_mail.py
2025-09-22 17:31:59 +00:00

151 lines
5.2 KiB
Python

import logging
from flask import Flask
from configs import dify_config
from dify_app import DifyApp
logger = logging.getLogger(__name__)
class Mail:
def __init__(self):
self._client = None
self._default_send_from = None
def is_inited(self) -> bool:
return self._client is not None
def init_app(self, _: Flask):
mail_type = dify_config.MAIL_TYPE
if not mail_type:
logger.warning("MAIL_TYPE is not set")
return
if dify_config.MAIL_DEFAULT_SEND_FROM:
self._default_send_from = dify_config.MAIL_DEFAULT_SEND_FROM
match mail_type:
case "resend":
import resend
api_key = dify_config.RESEND_API_KEY
if not api_key:
raise ValueError("RESEND_API_KEY is not set")
api_url = dify_config.RESEND_API_URL
if api_url:
resend.api_url = api_url
resend.api_key = api_key
self._client = resend.Emails
case "smtp":
from libs.mail import SMTPClient
if not dify_config.SMTP_SERVER or not dify_config.SMTP_PORT:
raise ValueError("SMTP_SERVER and SMTP_PORT are required for smtp mail type")
if not dify_config.SMTP_USE_TLS and dify_config.SMTP_OPPORTUNISTIC_TLS:
raise ValueError("SMTP_OPPORTUNISTIC_TLS is not supported without enabling SMTP_USE_TLS")
# Validate OAuth 2.0 configuration if auth_type is oauth2
oauth_access_token = None
if dify_config.SMTP_AUTH_TYPE == "oauth2":
oauth_access_token = dify_config.MICROSOFT_OAUTH2_ACCESS_TOKEN
if not oauth_access_token:
# Try to get token using client credentials flow
if dify_config.MICROSOFT_OAUTH2_CLIENT_ID and dify_config.MICROSOFT_OAUTH2_CLIENT_SECRET:
oauth_access_token = self._get_oauth_token()
if not oauth_access_token:
raise ValueError("OAuth 2.0 access token is required for oauth2 auth_type")
self._client = SMTPClient(
server=dify_config.SMTP_SERVER,
port=dify_config.SMTP_PORT,
username=dify_config.SMTP_USERNAME or "",
password=dify_config.SMTP_PASSWORD or "",
from_addr=dify_config.MAIL_DEFAULT_SEND_FROM or "",
use_tls=dify_config.SMTP_USE_TLS,
opportunistic_tls=dify_config.SMTP_OPPORTUNISTIC_TLS,
oauth_access_token=oauth_access_token,
auth_type=dify_config.SMTP_AUTH_TYPE,
)
case "sendgrid":
from libs.sendgrid import SendGridClient
if not dify_config.SENDGRID_API_KEY:
raise ValueError("SENDGRID_API_KEY is required for SendGrid mail type")
self._client = SendGridClient(
sendgrid_api_key=dify_config.SENDGRID_API_KEY, _from=dify_config.MAIL_DEFAULT_SEND_FROM or ""
)
case _:
raise ValueError(f"Unsupported mail type {mail_type}")
def _get_oauth_token(self) -> str | None:
"""Get OAuth access token using client credentials flow"""
try:
from libs.mail.oauth_email import MicrosoftEmailOAuth
client_id = dify_config.MICROSOFT_OAUTH2_CLIENT_ID
client_secret = dify_config.MICROSOFT_OAUTH2_CLIENT_SECRET
tenant_id = dify_config.MICROSOFT_OAUTH2_TENANT_ID or "common"
if not client_id or not client_secret:
return None
oauth_client = MicrosoftEmailOAuth(
client_id=client_id,
client_secret=client_secret,
redirect_uri="", # Not needed for client credentials flow
tenant_id=tenant_id,
)
token_response = oauth_client.get_access_token_client_credentials()
access_token = token_response.get("access_token")
return str(access_token) if access_token is not None else None
except Exception as e:
logging.warning("Failed to obtain OAuth 2.0 access token: %s", str(e))
return None
def send(self, to: str, subject: str, html: str, from_: str | None = None):
if not self._client:
raise ValueError("Mail client is not initialized")
if not from_ and self._default_send_from:
from_ = self._default_send_from
if not from_:
raise ValueError("mail from is not set")
if not to:
raise ValueError("mail to is not set")
if not subject:
raise ValueError("mail subject is not set")
if not html:
raise ValueError("mail html is not set")
self._client.send(
{
"from": from_,
"to": to,
"subject": subject,
"html": html,
}
)
def is_enabled() -> bool:
return dify_config.MAIL_TYPE is not None and dify_config.MAIL_TYPE != ""
def init_app(app: DifyApp):
mail.init_app(app)
mail = Mail()