-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlambda.py
More file actions
179 lines (148 loc) · 5.81 KB
/
Copy pathlambda.py
File metadata and controls
179 lines (148 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import email
import html
import logging
import os
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr, parseaddr
import boto3
s3 = boto3.client('s3')
ses = boto3.client('ses')
BUCKET = os.environ['S3_BUCKET']
PREFIX = os.environ.get('S3_PREFIX', 'inbound/')
FORWARD_TO = os.environ['FORWARD_TO']
FROM_ADDRESS = os.environ['FROM_ADDRESS']
VERBOSE_LOGGING = os.environ.get('VERBOSE_LOGGING', 'false').lower() == 'true'
log = logging.getLogger()
log.setLevel(logging.DEBUG if VERBOSE_LOGGING else logging.INFO)
def extract_email_content(original_msg):
"""
Extract plain text and HTML content from an email message.
"""
text_content = ""
html_content = ""
if original_msg.is_multipart():
for part in original_msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get('Content-Disposition', ''))
# Skip attachments
if 'attachment' in content_disposition:
continue
if content_type == 'text/plain':
text_content = part.get_payload(decode=True).decode(
'utf-8', errors='ignore'
)
elif content_type == 'text/html':
html_content = part.get_payload(decode=True).decode(
'utf-8', errors='ignore'
)
else:
# Simple single-part message
if original_msg.get_content_type() == 'text/plain':
text_content = original_msg.get_payload(decode=True).decode(
'utf-8', errors='ignore'
)
elif original_msg.get_content_type() == 'text/html':
html_content = original_msg.get_payload(decode=True).decode(
'utf-8', errors='ignore'
)
return text_content, html_content
def create_forwarding_context(orig_from, orig_to, orig_date):
"""
Create forwarding context message to prepend to the original content.
"""
context_text = f"""---------- Forwarded message ----------
From: {orig_from}
To: {orig_to}
Date: {orig_date}
"""
context_html = f"""<div style="border: 1px solid #ccc; padding: 10px; \
margin: 10px 0; background-color: #f9f9f9;">
<strong>---------- Forwarded message ----------</strong><br>
<strong>From:</strong> {html.escape(orig_from)}<br>
<strong>To:</strong> {html.escape(orig_to)}<br>
<strong>Date:</strong> {html.escape(orig_date)}<br>
</div>
"""
return context_text, context_html
def handler(event, context):
"""
Triggered by SES receipt rule (not S3). Raw email is stored as:
s3://BUCKET/PREFIX + SES.mail.messageId
"""
try:
record = event['Records'][0]
ses_record = record['ses']
mail = ses_record['mail']
receipt = ses_record['receipt']
message_id = mail['messageId']
# Get the actual recipient email from SES event
recipients = receipt['recipients']
actual_recipient = recipients[0] if recipients else ''
key = f'{PREFIX}{message_id}'
log.info('Fetching raw email from s3://%s/%s', BUCKET, key)
obj = s3.get_object(Bucket=BUCKET, Key=key)
raw_bytes = obj['Body'].read()
original = email.message_from_bytes(raw_bytes)
orig_from = original.get('From', '')
orig_date = original.get('Date', '')
subject = original.get('Subject', '')
# Extract original email content
text_content, html_content = extract_email_content(original)
# Create forwarding context
context_text, context_html = create_forwarding_context(
orig_from, actual_recipient, orig_date
)
# Extract display name from original sender for From header
orig_display_name, _ = parseaddr(orig_from)
formatted_from = formataddr((orig_display_name, FROM_ADDRESS))
# Create the multipart message
msg = MIMEMultipart('mixed')
msg['From'] = formatted_from
msg['To'] = FORWARD_TO
msg['Subject'] = subject
if orig_from:
msg['Reply-To'] = orig_from
# Create the main body with original content
body_multipart = MIMEMultipart('alternative')
# Add text version
if text_content:
full_text = context_text + text_content
else:
full_text = context_text + "(Original message had no text content)"
body_multipart.attach(MIMEText(full_text, 'plain', 'utf-8'))
# Add HTML version if available
if html_content:
full_html = context_html + html_content
body_multipart.attach(MIMEText(full_html, 'html', 'utf-8'))
elif text_content:
# Convert text to HTML if no HTML version exists
text_as_html = text_content.replace('\n', '<br>\n')
full_html = (
context_html
+ '<div style="white-space: pre-wrap; '
+ f'font-family: monospace;">{text_as_html}</div>'
)
body_multipart.attach(MIMEText(full_html, 'html', 'utf-8'))
# Attach the body to the main message
msg.attach(body_multipart)
# Attach the original email as reference
original_attachment = MIMEApplication(
raw_bytes, _subtype='rfc822', name='original.eml'
)
original_attachment.add_header(
'Content-Disposition', 'attachment', filename='original.eml'
)
msg.attach(original_attachment)
resp = ses.send_raw_email(RawMessage={'Data': msg.as_bytes()})
log.info(
'Forwarded %s -> %s; SES MessageId: %s',
message_id,
FORWARD_TO,
resp.get('MessageId'),
)
return {'status': 'ok'}
except Exception as e:
log.exception('Forward error: %s', e)
raise