イノベーション エンジニアブログ


株式会社イノベーションのエンジニアたちの技術系ブログです。ITトレンド・List Finderの開発をベースに、業務外での技術研究などもブログとして発信していってます!


このエントリーをはてなブックマークに追加

Route53 + SES + S3 + Lambda で 独自ドメイン宛のメールをGmailに転送する

SREチームの城田です。

今回は城田のプライベート環境の話ですが、
AWSを使った独自ドメイン宛のメールの転送設定を行ったので紹介します。

※当記事の内容はSESを利用しますが、何らかの理由でメールがループしてしまった場合の料金増加など、
当記事内容を利用したことによるトラブルに関して一切責任を負えませんのでご注意ください。

背景

  • 独自ドメイン宛のメール受信は、某ドメインレジストラのメール転送機能を使って、Gmailアドレス宛に転送していた。

  • その某ドメインレジストラからAWS Route53にドメイン移管したので、そのメール転送機能が使えなくなった。

  • メール転送をAWSで行うが、それだけの為にEC2にMTAを入れてメール転送するということはしたくない。

ということで、メール転送機能(と同等なもの)をサーバーレスで実装してみました。

やったこと

  • Route53でMXレコードの設定

  • SESで独自ドメインメールの受信設定

  • 受信したメールをS3に保存するよう設定

  • Lambdaスクリプトを作成

    • スクリプト内容:S3 Putをトリガーとして、メールを一部パース・置換し、SESでGmailにメール送信する

route53+ses+s3+lambda.png

Route53でMXレコードの設定

AWSコンソールのSESから、Domains → Verify New Domain と進みます。

cap001.png

Domain: {{ 独自ドメイン名 }} を入力して、 Generate DKIM Settings にチェック

cap002.png

Verify This Domain → Use Route 53

cap014.png

Email Receiving Record にチェック → Create Record Sets と進みます。

cap003.png

これで、独自ドメインのMXレコードとおまけにDKIMの設定も出来てしまいました。
Route53でドメインを管理するととても簡単です。

SESで独自ドメインメールの受信設定(+ 受信したメールをS3に保存)

AWSコンソールのSESから、Email Receiving → Rule Sets → Create a Receipt Rule と進みます。

cap005.png

受信したいメールアドレスを Add Recipient して、Next Step へ

cap007.png

Add action から S3 を選択します。

cap008.png

S3 bucket の Create S3 bucket からバケットを作成し、
作成したバケットを選択して Next Step へ

cap009.png cap010.png

ルール名を設定して Next Step クリック後の確認画面で Create Rule をクリックして、ルールを作成します。

cap011.png

これで、受信したいメールアドレス宛のメールを受信して、S3に保存されるようになりました。

また、SESから自分のGmailへメールを送信できるように、
Email Addresses → Verify a New Email Address → 自身のGmailアドレスを入力 → Verify This Email Address
で、Gmailへのメール配信を許可しておきましょう。

cap015.png

Lambdaスクリプトを作成

S3 Putをトリガーとして、メールを一部パースし、SESでGmailにメール送信するLambdaスクリプトを作成します。

  • バケット:先ほど作成したバケットを選択

  • イベントタイプ:オブジェクトの作成(すべて)を選択

  • トリガーの有効化にチェック

次へをクリック

cap012.png

  • 関数名:SES-Forwarder

  • ランタイム:Python2.7

  • Lambda関数のコード:※後述のソースコード

  • ロール:既存のロールを選択

  • 既存のロール:SES Full Accessの付いたIAMロール

  • タイムアウト:1分

cap013.png

ソースコード

import boto3
import email
import re

ORIGIN_TO  = "{{ 独自ドメインのメールアドレス }}"
FORWARD_TO = "{{ 転送先のGmailアドレス }}"
SES_REGION = "us-east-1"
S3_BUCKET  = "{{ メールを保存するバケット名 }}"

def parse_mail(raw_message):

    replaced_message = raw_message.replace(ORIGIN_TO, FORWARD_TO)
    replaced_message = re.sub("From:.+?\n", "From: %s\r\n" % ORIGIN_TO, replaced_message)
    replaced_message = re.sub("Return-Path:.+?\n", "Return-Path: %s\r\n" % ORIGIN_TO, replaced_message)

    return replaced_message

def send_mail(message):

    ses = boto3.client('ses', region_name=SES_REGION)

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

def lambda_handler(event, context):
    try:
        s3_key = event['Records'][0]['s3']['object']['key']

        s3 = boto3.client('s3')
        response = s3.get_object(
            Bucket = S3_BUCKET,
            Key    = s3_key
        )
        raw_message = response['Body'].read()
        message = parse_mail(raw_message)

        send_mail(message)

    except Exception as e:
        print(e)

ソースコード説明

以下の部分でS3イベントから該当のS3キーを取得して、
それをもとにS3に保存されたEml形式のデータを取得しています。

s3_key = event['Records'][0]['s3']['object']['key']

s3 = boto3.client('s3')
response = s3.get_object(
    Bucket = S3_BUCKET,
    Key    = s3_key
)
raw_message = response['Body'].read()

以下の部分でメールのパースを行っています。

message = parse_mail(raw_message)

やっていることは、
実はパース的なことがメインでなくて、メールの送信先をGmailに置換して、
メールヘッダーの From と Return-Path を、
送信者メールアドレスから独自ドメインのメールアドレスへ変更しています。
※Return-toヘッダーはそのままです。
※こうしないとSESの成りすまし制限に引っかかってしまいました。

def parse_mail(raw_message):

    replaced_message = raw_message.replace(ORIGIN_TO, FORWARD_TO)
    replaced_message = re.sub("From:.+?\n", "From: %s\r\n" % ORIGIN_TO, replaced_message)
    replaced_message = re.sub("Return-Path:.+?\n", "Return-Path: %s\r\n" % ORIGIN_TO, replaced_message)

    return replaced_message

以下の部分でメールを送信しています。

send_mail(message)

Eml形式のデータそのままで送信可能な、boto3 SDK の send_raw_email を使っているので、
大分簡単に書けました。

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

これで、サーバレスでRoute53管理ドメイン宛のメールをGmailに転送することができました。
※問題としては、純粋な転送ではなく、再送信を行っている為、メールのFromが自分になってしまうという部分です。。
※Reply-toヘッダーはそのまま送信者のメールアドレスなので、届いたメールに返信する時はもちろん送信者宛に返信できます。

所感

プライベートでの利用でしたらこれで良いのですが、
ビジネスでの利用となりますと、やはりメールサーバを立てるか専用サービスを使うべきと思いました。

以上です。