freeleaps-service-hub/apps/notification/backend/services/email/email_bounce_service.py

199 lines
7.1 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:
"""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