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: """handle bounce event, create email_id association""" try: # 1. find corresponding email record 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 # 2. update email status to bounced email_status_doc.status = EmailSendStatus.BOUNCED email_status_doc.updated_at = datetime.utcnow() await email_status_doc.save() 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 } ) # 3. create bounce record 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]: """get bounce info""" 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: """mark bounce as processed""" 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): """check if email is blacklisted""" 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