update
This commit is contained in:
parent
a3e86add49
commit
df0d92ba27
|
|
@ -11,10 +11,21 @@ sqs = boto3.client('sqs', region_name='us-east-2')
|
||||||
# AWS Region
|
# AWS Region
|
||||||
AWS_REGION = 'us-east-2'
|
AWS_REGION = 'us-east-2'
|
||||||
|
|
||||||
|
# Dynamo Table
|
||||||
|
dynamo = boto3.resource('dynamodb', region_name=AWS_REGION)
|
||||||
|
msg_table = dynamo.Table('ses-outbound-messages')
|
||||||
|
|
||||||
# Metadata Keys
|
# Metadata Keys
|
||||||
PROCESSED_KEY = 'processed'
|
PROCESSED_KEY = 'processed'
|
||||||
PROCESSED_VALUE = 'true'
|
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:
|
def domain_to_bucket(domain: str) -> str:
|
||||||
"""Konvertiert Domain zu S3 Bucket Namen"""
|
"""Konvertiert Domain zu S3 Bucket Namen"""
|
||||||
|
|
@ -323,20 +334,92 @@ def lambda_handler(event, context):
|
||||||
|
|
||||||
# E-Mail laden um Subject zu extrahieren
|
# E-Mail laden um Subject zu extrahieren
|
||||||
subject = '(unknown)'
|
subject = '(unknown)'
|
||||||
|
raw_bytes = b''
|
||||||
|
parsed = None
|
||||||
|
modified = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(f"\n📖 Reading email for metadata...")
|
print(f"\n📖 Reading email for metadata...")
|
||||||
obj = s3.get_object(Bucket=bucket, Key=key)
|
obj = s3.get_object(Bucket=bucket, Key=key)
|
||||||
raw_bytes = obj['Body'].read()
|
raw_bytes = obj['Body'].read()
|
||||||
|
metadata = obj.get('Metadata', {}) or {}
|
||||||
|
|
||||||
# Nur Headers parsen (schneller)
|
# Header parsen
|
||||||
parsed = BytesParser(policy=SMTPPolicy).parsebytes(raw_bytes)
|
parsed = BytesParser(policy=SMTPPolicy).parsebytes(raw_bytes)
|
||||||
subject = parsed.get('subject', '(no subject)')
|
subject = parsed.get('subject', '(no subject)')
|
||||||
|
|
||||||
print(f" Subject: {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:
|
except Exception as e:
|
||||||
print(f" ⚠ Could not parse email (continuing): {e}")
|
print(f" ⚠ Could not parse email (continuing): {e}")
|
||||||
|
|
||||||
|
|
||||||
# In Queue einreihen (EINE Message mit ALLEN Recipients)
|
# In Queue einreihen (EINE Message mit ALLEN Recipients)
|
||||||
try:
|
try:
|
||||||
print(f"\n📤 Queuing to {queue_name}...")
|
print(f"\n📤 Queuing to {queue_name}...")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue