194 lines
6.9 KiB
Python
194 lines
6.9 KiB
Python
from typing import Dict, Optional
|
||
from datetime import datetime
|
||
from common.log.module_logger import ModuleLogger
|
||
from backend.models.models import EmailBounceDoc, EmailSendStatusDoc
|
||
from common.constants.email import BounceType
|
||
|
||
|
||
class EmailBounceService:
|
||
def __init__(self):
|
||
self.module_logger = ModuleLogger(sender_id="EmailBounceService")
|
||
|
||
async def process_bounce_event(self, email: str, tenant_id: str, bounce_type: BounceType,
|
||
reason: str, message_id: str = None) -> Dict:
|
||
"""处理退信事件,建立email_id关联"""
|
||
try:
|
||
# 1. 查找对应的邮件记录
|
||
email_status_doc = await EmailSendStatusDoc.find_one(
|
||
EmailSendStatusDoc.recipient_email == email,
|
||
EmailSendStatusDoc.tenant_id == tenant_id
|
||
).sort(-EmailSendStatusDoc.created_at) # 获取最新的邮件记录
|
||
|
||
email_id = None
|
||
template_id = None
|
||
|
||
if email_status_doc:
|
||
email_id = email_status_doc.email_id
|
||
template_id = email_status_doc.template_id
|
||
await self.module_logger.log_info(
|
||
"Found email record for bounce",
|
||
properties={
|
||
"email": email,
|
||
"email_id": email_id,
|
||
"tenant_id": tenant_id
|
||
}
|
||
)
|
||
else:
|
||
await self.module_logger.log_warning(
|
||
"No email record found for bounce",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id
|
||
}
|
||
)
|
||
|
||
# 2. 创建退信记录
|
||
bounce_doc = EmailBounceDoc(
|
||
email=email,
|
||
tenant_id=tenant_id,
|
||
email_id=email_id, # 建立关联
|
||
template_id=template_id,
|
||
bounce_type=bounce_type,
|
||
reason=reason,
|
||
bounced_at=datetime.utcnow(),
|
||
original_message_id=message_id,
|
||
processed=False
|
||
)
|
||
|
||
await bounce_doc.save()
|
||
|
||
await self.module_logger.log_info(
|
||
"Bounce event processed successfully",
|
||
properties={
|
||
"email": email,
|
||
"email_id": email_id,
|
||
"tenant_id": tenant_id,
|
||
"bounce_type": bounce_type.value,
|
||
"reason": reason
|
||
}
|
||
)
|
||
|
||
return {
|
||
"email": email,
|
||
"email_id": email_id,
|
||
"tenant_id": tenant_id,
|
||
"bounce_type": bounce_type.value,
|
||
"reason": reason,
|
||
"processed": True
|
||
}
|
||
|
||
except Exception as e:
|
||
await self.module_logger.log_error(
|
||
"Failed to process bounce event",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id,
|
||
"error": str(e)
|
||
}
|
||
)
|
||
raise
|
||
|
||
async def get_bounce_info(self, email: str, tenant_id: str) -> Optional[Dict]:
|
||
"""获取退信信息"""
|
||
try:
|
||
bounce_doc = await EmailBounceDoc.find_one(
|
||
EmailBounceDoc.email == email,
|
||
EmailBounceDoc.tenant_id == tenant_id
|
||
).sort(-EmailBounceDoc.created_at)
|
||
|
||
if not bounce_doc:
|
||
return None
|
||
|
||
return {
|
||
"email": bounce_doc.email,
|
||
"email_id": bounce_doc.email_id,
|
||
"tenant_id": bounce_doc.tenant_id,
|
||
"template_id": bounce_doc.template_id,
|
||
"bounce_type": bounce_doc.bounce_type.value,
|
||
"reason": bounce_doc.reason,
|
||
"bounced_at": bounce_doc.bounced_at.isoformat(),
|
||
"original_message_id": bounce_doc.original_message_id,
|
||
"processed": bounce_doc.processed,
|
||
"processed_at": bounce_doc.processed_at.isoformat() if bounce_doc.processed_at else None,
|
||
"created_at": bounce_doc.created_at.isoformat()
|
||
}
|
||
|
||
except Exception as e:
|
||
await self.module_logger.log_error(
|
||
"Failed to get bounce info",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id,
|
||
"error": str(e)
|
||
}
|
||
)
|
||
raise
|
||
|
||
async def mark_bounce_processed(self, email: str, tenant_id: str) -> bool:
|
||
"""标记退信为已处理"""
|
||
try:
|
||
bounce_doc = await EmailBounceDoc.find_one(
|
||
EmailBounceDoc.email == email,
|
||
EmailBounceDoc.tenant_id == tenant_id
|
||
).sort(-EmailBounceDoc.created_at)
|
||
|
||
if bounce_doc:
|
||
bounce_doc.processed = True
|
||
bounce_doc.processed_at = datetime.utcnow()
|
||
await bounce_doc.save()
|
||
|
||
await self.module_logger.log_info(
|
||
"Bounce marked as processed",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id
|
||
}
|
||
)
|
||
return True
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
await self.module_logger.log_error(
|
||
"Failed to mark bounce as processed",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id,
|
||
"error": str(e)
|
||
}
|
||
)
|
||
raise
|
||
|
||
async def is_blacklisted(self, email: str, tenant_id: str) -> bool:
|
||
"""检查邮箱是否在黑名单中"""
|
||
try:
|
||
# 查找该邮箱的退信记录
|
||
bounce_doc = await EmailBounceDoc.find_one(
|
||
EmailBounceDoc.email == email,
|
||
EmailBounceDoc.tenant_id == tenant_id,
|
||
EmailBounceDoc.bounce_type == BounceType.HARD_BOUNCE # 只检查硬退信
|
||
)
|
||
|
||
is_blacklisted = bounce_doc is not None
|
||
|
||
await self.module_logger.log_info(
|
||
"Email blacklist check completed",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id,
|
||
"is_blacklisted": is_blacklisted
|
||
}
|
||
)
|
||
|
||
return is_blacklisted
|
||
|
||
except Exception as e:
|
||
await self.module_logger.log_error(
|
||
"Failed to check email blacklist",
|
||
properties={
|
||
"email": email,
|
||
"tenant_id": tenant_id,
|
||
"error": str(e)
|
||
}
|
||
)
|
||
return False |