Edited at

Amazon SESのアクションにLambdaを登録して独自ドメインメールをGmailとかに転送する

Gmail + AWSで独自ドメインのメールを受信する

 こちらの記事とやっていることはほぼ同じで、Lambda関数をS3のほうではなくてSESのアクションに設定しているだけです。

SESのeventに入っているmessageIdがobject keyになるように変更したり、環境変数を使ってみたりしてますが基本コピペです。

import os

import boto3
import email
import re
import logging

s3_client = boto3.client('s3')
ses_client = boto3.client('ses')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ORIGINAL_ADDRESS = os.environ.get('originalAddress', '')
FORWARD_ADDRESS = os.environ.get('forwardAdresses', '')
S3_BUCKET_NAME = os.environ.get('s3BucketName', '')
SES_REGION = 'us-east-1'

def parse_mail(raw_message):
from_name = 'No Name'

pattern = re.compile(r'^From:\s*(.+?)$', re.MULTILINE)
m = re.search(pattern, raw_message)
if m:
from_name = m.group(1)[:-1]

replaced_message = raw_message.replace(ORIGINAL_ADDRESS, FORWARD_ADDRESS)
replaced_message = re.sub(r'^From:\s*.+?$', r'From: "%s" <%s>' % (from_name, ORIGINAL_ADDRESS), replaced_message, flags=re.MULTILINE)
replaced_message = re.sub(r'^Return-Path:\s*.+?$', r'Return-Path: "%s" <%s>' % (from_name, ORIGINAL_ADDRESS), replaced_message, flags=re.MULTILINE)
replaced_message = re.sub(r'^Sender:\s*.+?$', r'Sender: "%s" <%s>' % (from_name, ORIGINAL_ADDRESS), replaced_message, flags=re.MULTILINE)

return replaced_message

def send_mail(message):
ses = boto3.client('ses', region_name=SES_REGION)

ses.send_raw_email(
Source = FORWARD_ADDRESS,
Destinations=[
FORWARD_ADDRESS
],
RawMessage={
'Data': message
}
)

def lambda_handler(event, context):
try:
s3_object = s3_client.get_object(
Bucket = S3_BUCKET_NAME,
Key = event['Records'][0]['ses']['mail']['messageId'])

raw_message = s3_object["Body"].read().decode('utf-8')
message = parse_mail(raw_message)
send_mail(message)

logger.info('SUCCESS')
except Exception:
logger.critical(
'Execution failed. Exception encountered',
exc_info=True)

logger.info('FAILED')

SES > Rule Sets > View Active Rule set > [rule name] でactionを追加したら完成です。


2019年6月28日追記

Amazonアソシエイトのユーザー追加確認メールなどDKIM-Signatureが2つ入っていたりするものが転送できない問題があった。

githubにもっとシンプルなものがあったのでこちらを使ったほうがよいと思う。

message_from_fileがStreamingBodyをうまく扱えなかったので、以下のようにmessage_from_stringに変更した。

from email import message_from_string

import json
import logging
import os
import re

import boto3
from botocore.exceptions import ClientError

FORWARD_MAPPING = {
'xxxxxxx@example.com': ['xxxxxxxx@gmail.com'],
}

VERIFIED_FROM_EMAIL = os.environ['VERIFIED_FROM_EMAIL'] # An email that is verified by SES to use as From address.
SES_INCOMING_BUCKET = os.environ['SES_INCOMING_BUCKET'] # S3 bucket where SES stores incoming emails.

s3 = boto3.client('s3')
ses = boto3.client('ses')

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
record = event['Records'][0]
assert record['eventSource'] == 'aws:ses'

o = s3.get_object(Bucket=SES_INCOMING_BUCKET, Key=record['ses']['mail']['messageId'])
raw_mail = o['Body'].read().decode('utf-8')

msg = message_from_string(raw_mail)

del msg['DKIM-Signature']
del msg['Sender']
del msg['Return-Path']

original_from = msg['From']
del msg['From']

msg['From'] = re.sub(r'\<.+?\>', '', original_from).strip() + ' <{}>'.format(VERIFIED_FROM_EMAIL)

if not msg['Reply-To']: msg['Reply-To'] = original_from
msg['Return-Path'] = VERIFIED_FROM_EMAIL
msg_string = msg.as_string()

for recipient in record['ses']['receipt']['recipients']:
forwards = FORWARD_MAPPING.get(recipient, [])
if not forwards:
logger.warning('Recipent <{}> is not found in forwarding map. Skipping recipient.'.format(recipient))
continue
#end if

for address in forwards:
try:
o = ses.send_raw_email(Destinations=[address], RawMessage=dict(Data=msg_string))
logger.info('Forwarded email for <{}> to <{}>. SendRawEmail response={}'.format(recipient, address, json.dumps(o)))
except ClientError as e: logger.error('Client error while forwarding email for <{}> to <{}>: {}'.format(recipient, address, e))