ForcePilot/backend/package/yuxi/channel/extensions/email_smtp/status.py
Kris 59c6caaa64 feat(email-smtp): 新增SMTP/IMAP邮件渠道插件
实现完整的邮件收发渠道,支持IMAP IDLE实时收信、SMTP发信,包含附件校验、重复消息去重、OAuth2认证、邮件内容解析与引用剥离、邮件发送限流与连接池等功能
2026-05-21 10:45:56 +08:00

65 lines
2.3 KiB
Python

from __future__ import annotations
import asyncio
import logging
from datetime import datetime, UTC
from .config import EmailSmtpConfigAdapter
from .smtp_client import probe_smtp
from .types import EmailAccount
logger = logging.getLogger(__name__)
class EmailSmtpStatusAdapter:
def __init__(self):
self._config_adapter = EmailSmtpConfigAdapter()
self._last_probe: dict[str, dict] = {}
async def probe(self, account: dict) -> bool:
account_dict = self._config_adapter.resolve_account(account.get("account_id", "default"))
email_acct = EmailSmtpConfigAdapter.make_email_account(account_dict)
smtp_ok = await probe_smtp(email_acct.smtp.host, email_acct.smtp.port, email_acct.smtp.use_tls)
imap_ok = await self._probe_imap(email_acct)
result = {
"status": "ok" if (smtp_ok and imap_ok) else ("degraded" if (smtp_ok or imap_ok) else "down"),
"smtp_connected": smtp_ok,
"imap_connected": imap_ok,
"probed_at": datetime.now(UTC).isoformat(),
"account_id": account.get("account_id", "default"),
}
self._last_probe[account.get("account_id", "default")] = result
return smtp_ok and imap_ok
def build_summary(self, snapshot: object) -> dict:
account_id = getattr(snapshot, "account_id", "default")
result = self._last_probe.get(account_id, {})
return {
"channel_type": "email-smtp",
"account_id": account_id,
"status": result.get("status", "unknown"),
"smtp_ok": result.get("smtp_connected", False),
"imap_ok": result.get("imap_connected", False),
"last_probe": result.get("probed_at", ""),
}
async def _probe_imap(self, account: EmailAccount) -> bool:
from imap_tools import MailBox
try:
def _check():
with MailBox(account.imap.host, account.imap.port).login(
account.imap.username, account.imap.password, initial_folder="INBOX"
) as mb:
status = mb.folder.status("INBOX")
return status is not None
return await asyncio.to_thread(_check)
except Exception as e:
logger.debug("IMAP probe failed: %s", e)
return False