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