2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Amazon SESを使うことで通常のGmailで独自ドメインを利用する -転送編-

Last updated at Posted at 2022-02-09

この記事について

準備編、受信編、送信編の続き
準備編

受信編

送信編

SESでのメール受信、GmailからSESを通じてのメール送信ができるようになった。あとはSESで受信したメールをGmailに転送してやれば完成のはずだだだ。

(2022/2/13追記)
ここまで進めてきてはみたものの、SESでは転送するメールのFromについても検証済みのドメインの必要があったり(エンベロープではどうにもできなかった。調査不足の可能性はある)、転送するメールのDKIMについても対応が難しくて結局別のメール転送ソリューションに頼ることにした。

送信はGmail → Amazon SES →
受信は→ Cloudflare Email Routing → Gmail
で一旦FIX。送受信ともに、SPF/DKIMは正しく扱われる。

Lambda

SESのEmail receivingのruleからLambdaが起動できるので、こいつを使ってSES経由でGmailに転送してやればOK。
起動したLambdaに渡されるeventにはメッセージ本体(body)は渡されないので、一旦S3に格納した受信メールをmessageIdを用いて参照する必要がある。

image.png

Lambdaの作成

では作っていきましょう!
「何も考えずにコードを実行する」Lambda。最高です。(考えろ)

image.png

「関数の作成」からスタート。__SESと同じリージョンで作成する__のを忘れないように。
(今回はバージニア北部 us-east-1)

image.png

「一から作成」を選択し、適当に関数名を入力。
ランタイムは利用できるPythonの最新として「Python 3.9」を選択。
アーキテクチャはお好きなものを。特に今回の処理はアーキテクチャに依存しないのでarm64にしときました。

image.png

アクセス権限の「デフォルトの実行ロールの変更」を開いて「既存のロールを使用する」を選択。
利用するIAMロールを作りに「IAMコンソール移動します」のリンクからIAMへ。

image.png

IAMロールの作成

  • 信頼されたエンティティ: 「AWSのサービス」
  • ユースケース: Lambda

image.png

「許可を追加」では特に何も選ばずに「次へ」
(あとでインラインポリシーをアタッチします)

image.png

ロール名を適当につけて作成。

image.png

作成したポリシーを選択して「アクセス許可を追加」から「インラインポリシーを作成」

image.png

JSONモードでポリシーを記述。

image.png

ポリシーは以下のようにした。
<accountId><bucketName>(あと下記ではus-east-1としてあるregion)については環境に合わせて編集。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:us-east-1:<accountId>:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:<accountId>:log-group:/aws/lambda/mailForwarding:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::<bucketName>/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
    ]
}

適当にポリシーの名前を記入して完了。

image.png

Lambdaの作成の続き

Lambda作成の画面に戻りましょう。
更新ボタンを押して先程作成したロールを選択します。
詳細設定は特に触れるところはないので、「関数の作成」へ。

image.png

メールの転送処理を書く。
やることとしては以下の3つ。

  • S3からメールデータを取得する
  • メールデータのヘッダを書き換える
  • SESで送信する

FromやReturn-PathがSESで検証済みのドメインでないと蹴られちゃうので、その辺りを書き換えるのがメイン。あとは転送先で返信しやすいよう、元々のFromをReply-Toにセットしてる。

試行錯誤の結果、一旦こうなっているがもっと色々弄るべきところはありそう。エイリアス対応したり、存在しないメールアドレス宛をどう扱うかを考えたり。 対応してみた。

あとは転送するとDKIM通らないとかその辺りが気になるところ。転送の際にメールヘッダのDKIMに関する部分を書き換えれば良いのだろうけれど、そもそも元のメールがどうだったかが大事だもんなぁ。対応保留。

lambda_function.py

import os
import boto3
import json
import re

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

BUCKETNAME = os.environ['BUCKETNAME']
FORWARDS = json.loads(os.environ['FORWARDS'])
DEFAULT = json.loads(os.environ['DEFAULT'])


def main(record):
    # get mail data from s3 bucket
    message_id = record['ses']['mail']['messageId']
    mail_object = s3.get_object(Bucket=BUCKETNAME, Key=message_id)
    message = mail_object['Body'].read().decode('utf-8')

    # get "To:","From:" From original mail headers
    destinations = record['ses']['mail']['destination']
    original_from = get_mail_header(record, 'From')

    # modify email headers
    # set original from address to Reply-To
    if get_mail_header(record, 'Reply-To') is None:
        message = add_mail_header(message, 'Reply-To', original_from)
    # set original from address to X-Original-Mail-From
    message = add_mail_header(message, 'X-Original-Mail-From', original_from)

    transfered = False
    for forward in FORWARDS:
        for destination in destinations:
            if forward['from'] == re.sub(r'\+[^@]+',  '', destination):
                mail_transfer(destination, forward['to'], original_from, message)
                transfered = True
    # if destinations did not match FORWARDS, then send to DEFAULT
    if not transfered:
        mail_transfer(DEFAULT['from'], DEFAULT['to'], original_from, message)


def get_mail_header(record, name):
    values = list(filter(
        lambda x: x['name'] == name, record['ses']['mail']['headers']
    ))
    return values[0]['value'] if values else None


def upd_mail_header(message, header, value):
    return re.sub(
        r'(^|\n)' + header + r': .*?(\n[^\s\t])',
        '\\1' + header + ': ' + value + '\\2',
        message, 1, flags=(re.DOTALL)
        )


def add_mail_header(message, header, value):
    return f'{header}: {value}\n' + message


def mail_transfer(from_addr, to_addr, original_from, message):
    # message modify
    message = message
    message = upd_mail_header(message, 'To', to_addr)
    message = upd_mail_header(message, 'From', from_addr)
    message = upd_mail_header(message, 'Return-Path', from_addr)

    result = send_mail(message, from_addr, to_addr)
    print(f'Transfer process complete: messageId {result}')


def send_mail(message, from_addr, to_addr):
    print(f'from {from_addr} to {to_addr}')
    print(message)
    return ses.send_raw_email(
        Source=from_addr,
        Destinations=[to_addr],
        RawMessage={'Data': message}
    )


def lambda_handler(event, context):
    for record in event['Records']:
        main(record)

環境変数の設定

設定 → 環境変数から

image.png

  • BUCKETNAME: メールデータを格納したバケットの名前
  • FORWARDS: JSONで記述したfromとtoの辞書リスト。fromに来たメールをtoに転送する。
    • [{"from": "foo@example.com", "to": "example@gmail.com"}]
  • DEFAULT: JSONで記述したfromとtoの辞書。FORWARDSに該当しなかった場合はここに転送する。
    • {"from": "transfer-agent@example.com", "to": "example@gmail.com"}

アクセス権限の追加

設定 → アクセス権限から
image.png

アクセス権限を追加

  • AWSのサービス
  • サービス: Other
  • ステートメント ID: 適当
  • プリンシパル: ses.amazonaws.com
  • ソース ARN: 呼び出し元となるSESルールのARN?
  • アクション: lambda:InvokeFunction

ごめん、ここでソースARNが?となっているのはCLIで設定したから。
なんでかCLIだとソースARNの指定がなくとも通るんよ……。

image.png

aws lambda add-permission --function-name mailForwarding \
--action lambda:InvokeFunction \
--statement-id ses \
--principal ses.amazonaws.com

これでLambdaをDeployすればOK。

SESの設定

SESのEmail receiving → 受信編で作成したルールセット → 受信編で作成したルールを選択して「Edit」

image.png

image.png

image.png

「Add actions」までNextで進める。
前回作成した「1. Deliver to Amazon S3 bucket」の次に「Invoke AWS Lambda function」を追加する。

  • Lambda function: 先ほど作成したLambda
  • Invocation type: Event Invocation
  • SNS topic: 指定なし

追加したらNextして「Save changes」

image.png

確認

Lambdaの環境変数のFORWARDSの"from"に設定したアドレスにメールを配信してみて、"to"に転送されれば成功! あかんときはCloudwatchLogsとにらめっこだ。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?