This commit is contained in:
Andreas Knuth 2025-11-15 12:48:20 -06:00
parent a3e86add49
commit df0d92ba27
2 changed files with 159 additions and 4 deletions

View File

@ -11,10 +11,21 @@ sqs = boto3.client('sqs', region_name='us-east-2')
# AWS Region
AWS_REGION = 'us-east-2'
# Dynamo Table
dynamo = boto3.resource('dynamodb', region_name=AWS_REGION)
msg_table = dynamo.Table('ses-outbound-messages')
# Metadata Keys
PROCESSED_KEY = 'processed'
PROCESSED_VALUE = 'true'
def is_ses_autoresponse(parsed):
from_h = (parsed.get('From') or '').lower()
auto_sub = (parsed.get('Auto-Submitted') or '').lower()
return (
'mailer-daemon@us-east-2.amazonses.com' in from_h
and 'auto-replied' in auto_sub
)
def domain_to_bucket(domain: str) -> str:
"""Konvertiert Domain zu S3 Bucket Namen"""
@ -323,19 +334,91 @@ def lambda_handler(event, context):
# E-Mail laden um Subject zu extrahieren
subject = '(unknown)'
raw_bytes = b''
parsed = None
modified = False
try:
print(f"\n📖 Reading email for metadata...")
obj = s3.get_object(Bucket=bucket, Key=key)
raw_bytes = obj['Body'].read()
# Nur Headers parsen (schneller)
metadata = obj.get('Metadata', {}) or {}
# Header parsen
parsed = BytesParser(policy=SMTPPolicy).parsebytes(raw_bytes)
subject = parsed.get('subject', '(no subject)')
print(f" Subject: {subject}")
# 🔁 SES Auto-Response erkennen
if is_ses_autoresponse(parsed):
print(" Detected SES auto-response (out-of-office)")
# Message-ID der ursprünglichen Mail aus In-Reply-To / References holen
in_reply_to = (parsed.get('In-Reply-To') or '').strip()
if not in_reply_to:
refs = (parsed.get('References') or '').strip()
# nimm die erste ID aus References
in_reply_to = refs.split()[0] if refs else ''
lookup_id = ''
if in_reply_to.startswith('<') and '>' in in_reply_to:
lookup_id = in_reply_to[1:in_reply_to.find('>')]
else:
lookup_id = in_reply_to
original = None
if lookup_id:
try:
res = msg_table.get_item(Key={'MessageId': lookup_id})
original = res.get('Item')
print(f" Dynamo lookup for {lookup_id}: {'hit' if original else 'miss'}")
except Exception as e:
print(f"⚠ Dynamo lookup failed: {e}")
if original:
orig_from = original.get('source', '')
destinations = original.get('destinations', []) or []
# einfache Variante: nimm den ersten Empfänger
orig_to = destinations[0] if destinations else ''
# Domain hast du oben bereits aus recipients[0] extrahiert
display = f"Out of Office from {orig_to}" if orig_to else "Out of Office"
# ursprüngliche Infos sichern
parsed['X-SES-Original-From'] = parsed.get('From', '')
parsed['X-SES-Original-Recipient'] = orig_to
# From für den User "freundlich" machen
parsed.replace_header('From', f'"{display}" <no-reply@{domain}>')
# Antworten trotzdem an den Absender deiner ursprünglichen Mail
if orig_from:
parsed['Reply-To'] = orig_from
subj = parsed.get('Subject', 'out of office')
if not subj.lower().startswith('out of office'):
parsed.replace_header('Subject', f"Out of office: {subj}")
# geänderte Mail zurück in Bytes
raw_bytes = parsed.as_bytes()
modified = True
print(" Auto-response rewritten for delivery to user inbox")
else:
print(" No original send record found for auto-response")
# Wenn wir die Mail verändert haben, aktualisieren wir das S3-Objekt
if modified:
s3.put_object(
Bucket=bucket,
Key=key,
Body=raw_bytes,
Metadata=metadata
)
print(" Updated S3 object with rewritten auto-response")
except Exception as e:
print(f" ⚠ Could not parse email (continuing): {e}")
# In Queue einreihen (EINE Message mit ALLEN Recipients)
try:

View File

@ -0,0 +1,72 @@
import boto3
import os
dynamo = boto3.resource('dynamodb', region_name='us-east-2')
table = dynamo.Table('ses-outbound-messages')
def lambda_handler(event, context):
detail = event['detail']
mail = detail['mail']
msg_id = mail['messageId']
source = mail['source']
destinations = mail['destination'] # Liste
if not msg_id:
print("No MessageId in event")
return
if event_type == 'SEND':
# wie bisher
source = mail.get('source')
destinations = mail.get('destination', [])
table.put_item(
Item={
'MessageId': msg_id,
'source': source,
'destinations': destinations,
'timestamp': mail.get('timestamp')
}
)
return
if event_type == 'BOUNCE':
bounce = detail.get('bounce', {})
bounced = [
r.get('emailAddress')
for r in bounce.get('bouncedRecipients', [])
if r.get('emailAddress')
]
if not bounced:
print("No bouncedRecipients in bounce event")
return
# in DynamoDB anhängen
table.update_item(
Key={'MessageId': msg_id},
UpdateExpression="ADD bouncedRecipients :b",
ExpressionAttributeValues={
':b': set(bounced) # String-Set
}
)
print(f"Updated {msg_id} with bouncedRecipients={bounced}")
return
if event_type == 'COMPLAINT':
complaint = detail.get('complaint', {})
complained = [
r.get('emailAddress')
for r in complaint.get('complainedRecipients', [])
if r.get('emailAddress')
]
if not complained:
return
table.update_item(
Key={'MessageId': msg_id},
UpdateExpression="ADD complaintRecipients :c",
ExpressionAttributeValues={
':c': set(complained)
}
)
print(f"Updated {msg_id} with complaintRecipients={complained}")
return