99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Bounce detection and header rewriting
|
|
"""
|
|
|
|
from typing import Tuple, Any
|
|
|
|
from logger import log
|
|
from aws.dynamodb_handler import DynamoDBHandler
|
|
|
|
|
|
class BounceHandler:
|
|
"""Handles bounce detection and header rewriting"""
|
|
|
|
def __init__(self, dynamodb: DynamoDBHandler):
|
|
self.dynamodb = dynamodb
|
|
|
|
@staticmethod
|
|
def is_ses_bounce_notification(parsed_email) -> bool:
|
|
"""Check if email is from SES MAILER-DAEMON"""
|
|
try:
|
|
from_header = (parsed_email.get('From') or '').lower()
|
|
except (AttributeError, TypeError, KeyError):
|
|
# Malformed From header - safely extract raw value
|
|
try:
|
|
from_header = str(parsed_email.get_all('From', [''])[0]).lower()
|
|
except:
|
|
from_header = ''
|
|
|
|
return 'mailer-daemon@' in from_header and 'amazonses.com' in from_header
|
|
|
|
def apply_bounce_logic(
|
|
self,
|
|
parsed,
|
|
subject: str,
|
|
worker_name: str = 'unified'
|
|
) -> Tuple[Any, bool]:
|
|
"""
|
|
Check for SES Bounce, lookup in DynamoDB and rewrite headers
|
|
|
|
Args:
|
|
parsed: Parsed email message object
|
|
subject: Email subject
|
|
worker_name: Worker name for logging
|
|
|
|
Returns:
|
|
Tuple of (parsed_email_object, was_modified_bool)
|
|
"""
|
|
if not self.is_ses_bounce_notification(parsed):
|
|
return parsed, False
|
|
|
|
log("🔍 Detected SES MAILER-DAEMON bounce notification", 'INFO', worker_name)
|
|
|
|
# Extract Message-ID from header
|
|
message_id = (parsed.get('Message-ID') or '').strip('<>').split('@')[0]
|
|
|
|
if not message_id:
|
|
log("⚠ Could not extract Message-ID from bounce notification", 'WARNING', worker_name)
|
|
return parsed, False
|
|
|
|
log(f" Looking up Message-ID: {message_id}", 'INFO', worker_name)
|
|
|
|
# Lookup in DynamoDB
|
|
bounce_info = self.dynamodb.get_bounce_info(message_id, worker_name)
|
|
|
|
if not bounce_info:
|
|
return parsed, False
|
|
|
|
# Bounce Info ausgeben
|
|
original_source = bounce_info['original_source']
|
|
bounced_recipients = bounce_info['bouncedRecipients']
|
|
bounce_type = bounce_info['bounceType']
|
|
bounce_subtype = bounce_info['bounceSubType']
|
|
|
|
log(f"✓ Found bounce info:", 'INFO', worker_name)
|
|
log(f" Original sender: {original_source}", 'INFO', worker_name)
|
|
log(f" Bounce type: {bounce_type}/{bounce_subtype}", 'INFO', worker_name)
|
|
log(f" Bounced recipients: {bounced_recipients}", 'INFO', worker_name)
|
|
|
|
if bounced_recipients:
|
|
new_from = bounced_recipients[0]
|
|
|
|
# Rewrite Headers
|
|
parsed['X-Original-SES-From'] = parsed.get('From', '')
|
|
parsed['X-Bounce-Type'] = f"{bounce_type}/{bounce_subtype}"
|
|
parsed.replace_header('From', new_from)
|
|
|
|
if not parsed.get('Reply-To'):
|
|
parsed['Reply-To'] = new_from
|
|
|
|
# Subject anpassen
|
|
if 'delivery status notification' in subject.lower() or 'thanks for your submission' in subject.lower():
|
|
parsed.replace_header('Subject', f"Delivery Status: {new_from}")
|
|
|
|
log(f"✓ Rewritten FROM: {new_from}", 'SUCCESS', worker_name)
|
|
return parsed, True
|
|
|
|
log("⚠ No bounced recipients found in bounce info", 'WARNING', worker_name)
|
|
return parsed, False |