技術メモ

プログラミングとか電子工作とか

EC2を使わないAWSでメール送信/受信(転送)(SES + S3 + Lambda)

f:id:ysmn_deus:20200215223446p:plain

Amazon WorkMail使えばええやんって所なんですが、無意味にメアドを増やしたい時なんかは 4.0 USD/Monthはちょい高め。
なので、勉強がてらSES、S3、Lambdaを使ったメール送受信を導入してみることにしました。
とはいえ、SESは本来送信専用なので受信はどこか普段使いのメールに転送する想定になります。

メール受信(転送)のセットアップ

まずはメールの受信から。
基本的にはAWSの公式ドキュメントを参照します。
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-getting-started.html
AWSで痛い目に遭いたくなければ日本語ドキュメントは読まないことです。古事記にもそう書かれている。

Step 1: 初期設定

まずSESの設定とかを始める前にやっておくことが2点あります。

  • AWSのアカウントを作っておく
  • Route53にドメインを登録しておく

基本的にこれらはできてる想定で話を進めます。

Step 2: ドメインの認証

SESでドメインを利用するに当たってドメインの認証が必要になります。
とりあえずSESのコンソールに行きましょう。
https://console.aws.amazon.com/ses/

リージョンは東京がないのでとりあえずバージニア北部(us-east-1)を想定してます。
SESのコンソールを開いたら「Identity Managemen」の「Domains」に行きます。 f:id:ysmn_deus:20200214133008p:plain f:id:ysmn_deus:20200214130405p:plain

Verify a New Domain

なんもないと思うので、左上の「Verify a New Domain」をクリックします。
「Verify a New Domain」というダイアログが出てくると思うので、「Domain」にRoute53に登録したドメインを入力し、「Generate DKIM Setting」にチェックを付けて「Verify This Domain」をクリックします。
DKIMに関しては設定してなくても基本いけると思いますが、送信先によってははじかれたりするので設定しておくのが無難かな、と思います。

「Verify This Domain」をクリックすると、ダイアログの内容が少々変わってなんかでてくると思います。
f:id:ysmn_deus:20200214131648p:plain
Route53を利用しない場合は表示されているレコードをドメインに設定する必要がありますが、AWSに命を捧げている者はAWS様がよしなにしてくれます。
ドメインのレコードをRoute53を利用しない人はここで個別に設定が必要ですが、Route53ユーザーであれば迷わず「Use Route 53」をクリックしましょう。

Use Route 53

次に「Use Route 53」というダイアログが出てきます。
前の段落で設定する筈だった項目をウィザードでやってくれます。
f:id:ysmn_deus:20200214132315p:plain
初めてドメインにレコードを設定する人であれば特に気にせず「Email Receiving Record」の部分もチェック入れて良いと思いますが、もし他にメールアドレスを登録してる人などはMXレコードが上書きされてしまうので要注意です。
全てにチェックを入れて「Create Record Sets」をクリックします。
するとSESのコンソールに戻り、設定していたドメインがpendingになってるのが分かります。
f:id:ysmn_deus:20200214132724p:plain
ここで5分ほど待ちます。待ってる間にRoute53で設定したドメインのレコードに先ほどのウィザードで設定したレコードが追加されているか確認してもいいと思います。

時間がたったらたぶんverifiedになってると思います。
f:id:ysmn_deus:20200214133008p:plain

Step 3: 受信ルールの作成

Step2でドメインの認証を済ませたら、受信設定を作成します。
SESのコンソールの「Email Receiving」の項目から「Rule Sets」をクリックし、「Create a Receipt Rule」をクリックしましょう。
f:id:ysmn_deus:20200214133617p:plain

受信相手の設定

最初に出てくるのは受信対象の設定です。
もし受信するドメインを限定したりする場合はこの辺を設定しましょう。
たぶん設定する人は少ないと思うので、特になにも入力せず「Next Step」をクリックしましょう。
f:id:ysmn_deus:20200214133857p:plain

保存先の設定(Actionの設定)

メールが来たときにそのメールをどうするかの挙動(Action)を設定します。
「Add action」から「S3」を選びます。
f:id:ysmn_deus:20200214134325p:plain
f:id:ysmn_deus:20200214134336p:plain

まずはS3を用意してないと思いますので、「Create S3 bucket」を選択します。(もし既に作成済みなら該当するbucket名を選んでください)
f:id:ysmn_deus:20200214134603p:plain
適当なbucket名を入力します。
f:id:ysmn_deus:20200214140249p:plain
(email-ドメイン名、など分かりやすい名称がいいとは思います。)

prefixなど設定できますがたぶんS3のパフォーマンスを落とすだけなので無視しましょう。
「Next Step」をクリックします。

ルールの詳細設定(名前決めるだけ)

「Rule Details」の項目が出てきます。
基本的に「Rule name」の箇所だけ適当に入力して、「Next Step」で問題無いかと思います。

最終確認

今まで設定してきた項目が一覧で確認できます。
何か間違いがあればここで修正できます。大丈夫であれば右下の「Create Rule」でルールを作成します。
f:id:ysmn_deus:20200214140952p:plain

問題無ければSESのコンソール上で表示されている筈です。
f:id:ysmn_deus:20200214165126p:plain

Step 4: メール送信テスト

まだ設定半ばなのですが、一端ここまでの設定が正しいか確かめてみます。
ドメイン名の前は何でも良いので、「test@設定したドメイン」のように適当なアドレスに向けてメールを送ってみます。
(例えば、example.comというドメインを設定したなら「test@example.com」のような。@の前は何でも良い)

Step 5: 受信したメールを見てみる

設定がうまくいっていれば、先ほどテストで送ったメールがS3に保存されている筈です。
S3のコンソール( https://console.aws.amazon.com/s3/ )から先ほど設定したbucketを見つけて中を見てみましょう。
f:id:ysmn_deus:20200214170821p:plain
bucketの中には - AMAZON_SES_SETUP NOTIFICATION - 謎の英数字の羅列 の2ファイルが存在していると思います。
謎の英数字の羅列が保存されたメールとなります。ダウンロードして中身を確認してみると、メールのソースになっていると思います。
(いわゆる本文だけではなく送信元情報などのデータとしてのメール。)

これでメールの受信が機能している所までは良さそうです。

Step 6: S3にPUTされたらLambdaで転送する様にする

S3にあがるだけではIMAPPOP3形式で受信できないので、どこかのメーラーでやりとりしたいものです。
(例えばGmailThunderbirdなど)
本来設定しているメールアドレスに一時的に転送して、閲覧は普段使いのメーラーでできるようにします。

転送用のLambda関数を作成する

何はともあれLambda関数が必要です。SAM CLIなどでデプロイしてもいいんですがそんなにいじらないし全部ブラウザ上から済ませます。

ローカルにaws-lambda-ses-forwarderをcloneする

まずローカルにGithubで公開されている aws-lambda-ses-forwarder をcloneします。

git clone https://github.com/arithmetric/aws-lambda-ses-forwarder

使い捨てるので適当なディレクトリで大丈夫だと思います。
f:id:ysmn_deus:20200214172950p:plain
必要なのはindex.jsのみです。(なんならコピペでもいいんですが・・・)

コンソールでLambda関数を作成する

ソースが準備できたらLambdaのコンソールに行きます。
https://console.aws.amazon.com/lambda/home
右上の「関数の作成」からLambda関数を作成します。
f:id:ysmn_deus:20200214173333p:plain

「一から作成」で基本的な情報を入力していきます。
関数名は適宜決めていただいて(例えば、「sesForwarder」など)、ランタイムは、現在(2020/02/14)ではNodeのv12が利用可能ですが、念のためv10にしておきました。(GithubのREADME上にはv8 v10と記載があるので、多分v12でも大丈夫だけど)
実行ロールは後で編集するとして、特に触らずに「関数の作成」でLambda関数を作成します。
f:id:ysmn_deus:20200214173938p:plain

とりあえずLambda関数はできました。
f:id:ysmn_deus:20200214174259p:plain

作成した関数をaws-lambda-ses-forwarderに書き換える

先ほどcloneしたaws-lambda-ses-forwarderの中にある「index.js」をアップロードします。
めんどくさい人はエディタで開いてコピーしてからブラウザ上のエディタに貼り付けても良いと思います。
とりあえずzipに固めてアップロードする手法を採ります。「index.js」のみzipに固めます。
f:id:ysmn_deus:20200214174436p:plain

固めたzipをLambdaにアップロードします。
先ほどのコンソールから「関数コード」という項目を探しだし、「コードエントリタイプ」から「zipファイルをアップロード」を選択します。
f:id:ysmn_deus:20200214174611p:plain

「アップロード」という箇所が出てくるので、先ほどのzipをアップロードする。
f:id:ysmn_deus:20200214174744p:plain

「アップロード」の横にzipファイル名が記載されていればアップロードできているので、「保存」をクリックしてアップロードしたzipファイルを関数に適応する。
f:id:ysmn_deus:20200214174934p:plain

Lambdaのコンソール上のエディタにaws-lambda-ses-forwarderが表示されていればOK
f:id:ysmn_deus:20200214175114p:plain

aws-lambda-ses-forwarderの設定を書き換える

これでアップロードは完了しましたが、ちょっとだけ設定を変更します。
30行目のdefaultConfigの値を変更していきます。

var defaultConfig = {
  fromEmail: "noreply@example.com", // ここは設定したドメインの任意のメールアドレス
  subjectPrefix: "[Forwarding]", // 転送するタイトルの頭に何か付けるならここに設定
  emailBucket: "s3-bucket-name", // emailが格納されるS3のbucket名
  emailKeyPrefix: "", // SESの設定でprefixを設定している場合は設定。上記の通りであれば空でOK
  forwardMapping: {
    "info@example.com": [
      "example.john@hoge.com",
      "example.jen@hoge.com"
    ],
    "@example.com": [
      "example.john@hoge.com"
    ]
  }
};

SESは仕様上、転送するメールアドレスの差出人を自分のドメイン以外にすることができません。
つまり、「hogefuga@hoge.com」からメールが来た場合に、「example.john@hoge.com」へメールを転送するのですが、上記の設定では、「example.john@hoge.com」へ届くメールの差出人は「noreply@example.com」に書き換わっています。

f:id:ysmn_deus:20200214182932p:plain
図示するとこんなかんじ

forwardMappingのみ追加で説明しますと、上に記載されているものから順にマッチするか評価され、最初にマッチした宛先に転送されるようです。
つまり上の例では - info@example.comはexample.john@hoge.comとexample.jen@hoge.comに転送される - info2@example.comはexample.john@hoge.comのみに転送される という処理になります。
基本的には1対1か1個のメールアドレスに集約されるように書くのがいいんじゃないでしょうか。

変更した後は右上の「保存」をクリックすることをお忘れ無きよう。

Lambda関数のロールの設定

現状ただのLambda関数ですのでSESはおろかS3にアクセスすらできません。
なので - S3へのアクセス権限 - SESでの送信権限 - ついでにCloudWatchのログ作成権限 を付与します。

コンソール上の下の方にある「実行ロール」という項目を確認します。
f:id:ysmn_deus:20200214184038p:plain

「既存のロール」という項目の下にある「【関数名】-role-(英数字の羅列)ロールを表示」という箇所をクリックします。
するとIAMのコンソールが表示されますので、「インラインポリシーの追加」を押します。
f:id:ysmn_deus:20200214184359p:plain

「ポリシーの作成」というページが表示されますので、編集していきます。
設定する対象がわかりきっている場合はJSONの方が早いです。「JSON」をクリックして下記のように設定しましょう。
f:id:ysmn_deus:20200214184556p:plain

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
         ],
         "Resource": "arn:aws:logs:*:*:*"
      },
      {
         "Effect": "Allow",
         "Action": "ses:SendRawEmail",
         "Resource": "*"
      },
      {
         "Effect": "Allow",
         "Action": [
            "s3:GetObject",
            "s3:PutObject"
         ],
         "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*"
      }
   ]
}

S3-BUCKET-NAMEの箇所はemailが保存されているS3のbucket名に変更して下さい。
厳密に言えばSESのリソースなども対象を絞った方がセキュアだと思いますが、その辺は割愛します。

JSONの編集が完了したら、右下の「ポリシーの確認」をクリックします。

最後に、設定したポリシーがどのようなものか一覧で表示されます。
ポリシー名を付けないといけないので「名前」の箇所を「LambdaSesForwarder」としておきました。
f:id:ysmn_deus:20200214185106p:plain

問題無ければ「ポリシーの作成」をクリックして完了します。
また別のLambdaに別のメールアドレスを設定する可能性などある場合はポリシーを作成しておくと再び作成しなくていいのですが、どうせS3のbucketも変わると思いますのでインラインポリシーで良いと思います。

Lambda関数のタイムアウト時間の変更

大丈夫な気がするんですが、一応aws-lambda-ses-forwarderのREADMEによると「メモリ制限が128MBで10秒がええんとちゃうか?」のような記述があるので、タイムアウト時間だけ10秒に変更しておきます。
(大きな添付ファイルが想定される場合はメモリを512MBにするか、タイムアウトを30秒にするかした方がいいかも、とも書かれているので心配な方は30秒に設定しておいた方がいいかも)

設定を変える場所は先ほど編集していた「実行ロール」の隣にあると思います。「基本設定」という項目を見つけ「編集」をクリックして下さい。
f:id:ysmn_deus:20200214185949p:plain

タイムアウトを10秒に変更して、「保存」をクリックします。
f:id:ysmn_deus:20200214190059p:plain

SESの設定にLambda関数を追加する

ここまできてようやくSESとLambdaを連携させます。
「保存先の設定(Actionの設定)」で編集していたSESのコンソールの「Email Receiving」→「Rule Sets」を編集します。
今までの通りにやっていれば「default-rule-set」が適応されていると思いますので、「View Active Rule Set」をクリックし、設定したRule(たぶん一番上に表示されてるヤツ)をクリックして編集します。
f:id:ysmn_deus:20200214191042p:plain
f:id:ysmn_deus:20200214191143p:plain

Add actionからLambdaを選択します。
f:id:ysmn_deus:20200214191317p:plain

対象の関数は作成した関数名を入力すれば出てくると思います。
それ以外は基本デフォルトで。
f:id:ysmn_deus:20200214191600p:plain

右下の「Save Rule」を押して編集したルールを保存します。
Missing Permissionsと聞かれますので、「Add permissions」で作成したSESにlambdaを起動する権限を付与します。

SESの設定で転送先のメールアドレスを認証しておく(一時的なもの)

おそらくスパム対策だと思いますが、初期状態ではSES+Lambdaでメールを送信する場合は転送先のメールアドレスを前もって認証しておく必要があります。
(後述する AWSのサポートへ連絡 でどのメールアドレスにも送信できるようになります。)
試験的にメールを送信するために、SESのコンソールから転送対象の認証を済ませます。
コンソールの「Identify Management」→「Email Addresses」をクリックし、「Verify a New Email Address」をクリックします。
f:id:ysmn_deus:20200214194422p:plain

Lambda関数のところで転送先を編集したと思うので、そのアドレスを入力します。
(上記の例では「example.john@hoge.com」と「example.jen@hoge.com」の二つ)
f:id:ysmn_deus:20200214194635p:plain

「Verify This Email Address」をクリックすると、前の画面に戻ってpendingになっているのが確認できます。
おそらく「Amazon Web Services – Email Address Verification Request...」というタイトルのメールが届いていると思いますので、記載されているリンクを24時間以内にクリックします。
「検証に成功しました」的な旨のページが出ればOKです。
f:id:ysmn_deus:20200214195036p:plain

コンソール上で先ほど設定したメールアドレスがpendingからverifiedに変わっていれば転送の準備は完了です。
f:id:ysmn_deus:20200214195239p:plain

転送されるか試しにメールを送ってみる

とりあえずメールを送りましょう。宛先は何でも良いと思いますが、Lambda関数の中でマッピングを複雑にした場合は色々試してみると良いと思います。

EX: S3のライフサイクルポリシー

上記のままだとメールが未来永劫たまり続けます。
基本残したままで困る容量ではないですが、SES+Lambdaで究極のコスパを攻めるならS3には極力データは残しておかない方がいいかもしれません。
S3にはライフサイクルポリシーという生成されてからどの期間残しておくかが設定できますので、そちらを設定しておけば極力容量は抑えられるとは思います。
ここでは割愛しますが、需要があればそのうち追記します。

メール送信のセットアップ

と項目を書いたのですが、受信のセットアップが終わっていればほぼ完了しているも同然です。
とはいえまだSMTPの情報などがないのでセットアップしていきましょう。

SMTPの情報を取得する

SESのコンソールから「Email Sending」→「SMTP Settings」をクリックします。
表示されている「Create My SMTP Credentials」をクリックします。
f:id:ysmn_deus:20200214231439p:plain

「Create User for SMTP」というページが出てくるので、とりあえずIAM User Nameはデフォルトで入ってる適当な名称を利用します。

f:id:ysmn_deus:20200214231952p:plain
特に変更しない

右下の「作成」でSMTPのユーザーを作成します。
f:id:ysmn_deus:20200214232107p:plain

書いてあるとおり、認証情報はこのタイミングでしかダウンロードできないので慎重に保存しておきます。
最悪ユーザーを作り直せば良いだけだと思いますが、くれぐれもこの辺の認証情報を公開しないように注意しましょう。
上記の画像に書いてある「ユーザーのSMTPセキュリティ認証情報を表示」でユーザー名とパスワードを表示して設定してもいいですが、ファイルに保存しておいた方が確実かと思いますので、右下の「認証情報のダウンロード」から認証情報を保存しておきましょう。

試験的に送信してみる

先ほどのSMTPの情報で、試しに認証している(上記までの例では、転送先のメールアドレス)にメールを送信してみて下さい。
おそらくSMTPのポートを587に設定して上記の情報を利用すれば送信できると思います。

Sandbox外への払い出し

SESのサービスを利用し始めたときは、認証したメールアドレスにしかメールを送ることができません。
(転送だけしかしない場合はこれでも問題ないでしょう)
なので、認証してないメールアドレスにも送信できるようにAWSに申請する必要があります。

AWSのサポートへ連絡

サポートに連絡するためにコンソール( https://console.aws.amazon.com/ )を開きます。
右上にある「サポート」から「サポートセンター」を開きます。
f:id:ysmn_deus:20200215091101p:plain

「Create case」をクリックし、「Service limit increase」を選択します。
f:id:ysmn_deus:20200215091414p:plain

すると、「Case classification」というボックスが出てくると思いますので、「Limit type」の「SES 送信制限」を選択します。
f:id:ysmn_deus:20200215095237p:plain
その他の入力項目は適宜合わせて貰えば良いと思いますが、今回は普段使いのメールなので「メールの種類」を「その他」、「ウェブサイトのURL」を空欄、残りの項目を「はい」にしました。
最後の3項目は日本語だとなんのこっちゃ・・・という感じですが、原文は

  • For My email sending complies with the AWS Service Terms and AUP, choose the option that applies to your use case.
  • For I only send to recipients who have specifically requested my mail, choose the option that applies to your use case.
  • For I have a process to handle bounces and complaints, choose the option that applies to your use case.

と記載されていますので

  • AWSサービス条件とAUP(ウェブサイトでリンクがありますのでご確認ください)を満たしているか
  • 受け取る意思がある人にのみメールを送信するか(メールのやりとりなので、よっぽど特殊なケースでない限りはあるはず。不特定多数の営業とかには使っちゃダメですね。)
  • 苦情などが来たときにハンドリングする手順があるか(普通のメールなので基本的には考えなくて良いと思います)

といった感じでしょう。アプリケーションなどにも活用を考えている場合はこのへんは慎重に回答してください。

次に、「Requests」の項目を埋めます。
リージョンを自分のSESのリージョンに合わせ、「Limit」を「希望する一日あたりの送信クォータ」に設定し、「New limit value」を「200」に設定します。この200は公式ドキュメントに書いてあります。 基本的にはドキュメント通りに設定すれば問題無いと思います。1日に200通以上も送る(受信ではなく送信)人は通常の人ではないので僕は知りません。
f:id:ysmn_deus:20200215100127p:plain

次に、「Case description」を記入します。
基本的にはありのままを記載すれば良いと思いますが、「Case classification」ではいと答えている項目には言及しておいた方がいいかもしれません。
自分は普通にメールとして使うよ!ということと、苦情対策としてはCloudWatchでSESの送信制限を監視して、ヤバそうだったら利用を停止するよ!ということを書いておきました。

最後に「Contact options」ですが、基本的にはデフォルトのままで良いと思います。「Preferred contact language」はもしかしたら英語の方がレスポンスが早いのかなぁとも思ったんですが、その後でやりとりが発生する可能性を考慮して一応日本語にしておきました。
「submit」を押して起票完了です。
f:id:ysmn_deus:20200215100956p:plain

たぶん1日ぐらいで返信が来ます。(自分は約1日で来ましたが、2~3日たっても気長に待ちましょう。)
「お客様のアカウントを Amazon SES サンドボックスから移動いたしました。」との記載があれば問題無く利用できる状態になっていると思います。