サーバーレスでメール検索システムを作ってみる
Author : 福嶋 一史
こんにちは。プロトタイピングソリューションアーキテクトの福嶋です。
私には小学生の娘がいまして、最近小学校が再開されたその娘から、ずっと家にいられていいなーと言われてたりします。仕事しているんですけどね。
さて、その娘が通っている学校からは、ほぼすべてメールで連絡が送られてきます。例えば、そのメールには登校日の持ち物が書かれています。メールを読んだときは分かったつもりになっているのですが、登校日の前日には忘れていて、そのメールを探すのにいつも苦労しています。困ったことにメールには PDF が添付されているので、一つ一つ開いて確認していくしかありません。
そんなメールを検索できたら楽なのにということで、AWS で作ってみました。

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »
毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。
アーキテクチャ
このシステムのアーキテクチャは、このようになっています。

Amazon SES でメールを受信して検索インデックスを作ることで、自動的に検索データを溜めることができます。
メールを受信して検索インデックスを作るまでの流れ
- Amazon SES がメールを受信する (上図 1)
- Amazon S3 バケットにメールデータを書き出す (上図 2)
- Amazon S3 にメールデータが格納されると、それをトリガーにして AWS Lambda が実行される (上図 3)
- AWS Lambda でメールデータを解析し、Amazon Elasticsearch Service にインデックスを作成する (上図 4)
検索処理の流れ
- クライアントが Amazon API Gateway に検索リクエストを送信する (上図 5)
- Amazon API Gateway が AWS Lambda を実行する (上図 6)
- AWS Lambda が Amazon Elasticsearch Service で検索して結果を返す (上図 7, 8, 9)
- Amazon API Gateway がクライアントに結果を返す (上図 10)
各サービスの概要
Amazon SES
Amazon SES (Amazon Simple Email Service) は、コスト効率の高いクラウドベースの E メール送受信サービスです。詳しくは、Amazon SES 紹介ページをご覧ください。
Amazon S3
Amazon S3 (Amazon Simple Storage Service) は、ユーザがデータを安全に、容量制限なく、データ保存が可能なクラウドオブジェクトストレージです。詳しくは、Amazon S3 紹介ページをご覧ください。
AWS Lambda
AWS Lambda は、サーバーのプロビジョニングしたり管理したりする必要なく、コードを実行できるコンピューティングサービスです。詳しくは、AWS Lambda 紹介ページをご覧ください。
Amazon Elasticsearch Service
Amazon Elasticsearch Service は、Elasticsearch を大規模かつ簡単でコスト効率の良い方法を使用してデプロイ、保護、実行する完全マネージド型サービスです。詳しくは、Amazon Elasticsearch Service 紹介ページをご覧ください。
Amazon API Gateway
Amazon API Gateway は、あらゆる規模の REST、HTTP、WebSocket API を作成、公開、管理、モニタリング、保護するためのサービスです。詳しくは、Amazon API Gateway 紹介ページをご覧ください。
前提
- 受信用メールアドレス
- SES が受信するメールアドレスとして使用します。
- そのドメインの DNS レコードを追加できる権限が必要です。
- メールアドレスがない方でも、SES の設定はスキップして、S3 バケットに MIME 形式のメールデータをアップロードすることでも、検索インデックスの作成やその検索は試していただけます。
- Maven (3.6 以上)、Java SDK (8 以上)
- Lambda 用の Java コードをビルドするために必要です。
ガイド
それでは、各サービスについて開発を進めていきましょう。
あらかじめサンプルコードをこちらからダウンロードしておいてください。
メール受信用の Amazon S3 バケットの作成
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<バケット名>/*",
"Condition": {
"StringEquals": {
"aws:Referer": "<アカウント ID>"
}
}
}
]
}
<バケット名> と <アカウント ID> をそれぞれ、上記で作成した S3 バケットの名前と、使用する AWS アカウント ID に置き換えてください。
Amazon SES でメール受信設定
次に、Amazon SES で、メールを受信し、上記で作成した S3 バケットに書き込む設定をしていきます。
Amazon SES で、メールを受信できるリージョンは、現時点で、米国東部 (バージニア北部)、米国西部 (オレゴン)、欧州 (アイルランド) となっていますので、このいずれかのリージョンを選択してください。なお、作成した S3 バケットとリージョンが違っていても大丈夫です。
まず、Amazon SES のメール受信で扱うドメインを登録します。Amazon SES コンソールを開き、Domains メニューを開きましょう。次に、表示される『Verify a New Domain』を押します。
対象のドメインを入力して、『Verify This Domain』を押します。例えば、メールアドレスが buildersflash@example.com の場合は、example.com のドメインを入力することになります。
『Verify This Domain』を押すと、このドメインの DNS 設定で TXT レコードと MX レコードの案内が表示されます。
このドメインの DNS 設定にて、指定されている値の TXT レコードと MX レコードを追加してください。この設定方法は、ドメインを管理しているサービスによって異なります。
なお、このドメインが Route 53 で管理されているドメインの場合は、右下の『Use Route 53』を押すことで、自動的に TXT レコードと MX レコードが追加することができます。
Route 53 を使用する場合、『Use Route 53』を押すと、次のダイアログが表示されます。ここで『Email Receiving Record』にチェックを入れ、『Create Record Sets』を押します。これにより、必要な TXT レコードと MX レコードが追加されます。
TXT レコードと MX レコードを追加したら、Amazon SES によるドメインの検証が終わるまで待ちます。
『Vefification Status』が『pending verification』から『verified』に変われば OK です。
これで、ドメインの登録が終わりました。次は、受信したメールを S3 バケットに保存する設定を入れていきましょう。
同じく SES コンソールの Rule Sets メニューを開き、『Create a Receipt Rule』を押します。
ここまでの設定について、動作確認してみます。SES に設定したメールアドレスに対して、メールを送ってみましょう。設定に問題がなければ、S3 バケットにメールデータが入っているはずです。このファイルをダウンロードして開いてみてください。送信したメールが受信されていることを確認できると思います。
Amazon Elasticsearch Service ドメインの作成
次に、検索サービスとして、Amazon Elasticsearch Service のドメインを作っていきます。
Amazon Elasticsearch Service コンソールのダッシュボードを開き、『新しいドメインの作成』を押します。
Elasticsearch ドメイン名に適当な名前を入力します。また、パフォーマンスや可用性は重要ではないため、インスタンスタイプも『t2.small.elasticsearch』にします。
次の画面では、ドメインにアクセスできる範囲を VPC 内に制限するため、ネットワーク構成を『VPC アクセス』を選択します。合わせて、ドメインを配置する VPC、サブネットを指定します。
また、ドメインアクセスポリシーでは『ドメインへのオープンアクセスを許可』を選択します。オープンアクセスを許可した場合でも、ドメインへのアクセスは、VPC からのみに制限されています。なお、VPC からのアクセスはセキュリティグループでコントロールできます。
これで、ドメインの作成が始まります。ドメインのステータスが『アクティブ』になるまで待ちましょう。また、この画面で表示されている VPC エンドポイントは、後ほどこのドメインに接続するために必要になるので、メモしておきます。
これで Amazon Elasticsearch Service のドメインが作成されました。
Amazon Elasticsearch Service ドメインへのアクセス用 EC2 インスタンスの作成
次に、Amazon Elasticsearch Service に対して、インデックスの設定をしていきます。そのためには、ドメインの VPC エンドポイントにアクセスする必要があります。しかし、ドメインをネットワーク構成を VPC で作成しているため、インターネット経由でのアクセスができません。このため、VPC 内にドメイン操作用の EC2 インスタンスを立てて、そこからコマンドを実行することになります。
ネットワークに Amazon Elasticsearch Service のドメイン作成で指定した VPC を選択します。サブネットは、その VPC 内のパブリックなサブネットを選択してください。自動割り当てパブリック IP は有効にします。最後に『次のステップ: ストレージの追加』を押します。
セキュリティグループのインバウンドルールでは、アクセス元からの SSH を許可します。ソースで『マイ IP』を選ぶと、現在このコンソールにアクセスしている環境の IP アドレスが自動で入力されます。このルールを追加したら、『確認と作成』を押します。
インスタンスの一覧画面で、起動したインスタンスのステータスが『running』に、ステータスチェックが『2/2 のチェックに合格しました』になれば、起動完了です。このインスタンスに対して SSH で接続するので、説明タブ内の IPv4 パブリック IP をメモしておきます。
Amazon Elasticsearch Service ドメインでインデックス作成
上記で作成した EC2 インスタンスに SSH 接続します。接続方法はこちらをご参考ください。
SSH 接続できたら、cURL コマンドで、メモしておいた、Amazon Elasticsearch Service ドメインの VPC エンドポイントにリクエストを投げてみます。
$ curl -X GET https://<ドメインの VPC エンドポイント>
このようなレスポンスが返って来れば、ドメインへの接続ができています。
{
"name" : "2ad6b898d7a65a232d2c0de3d05388b3",
"cluster_name" : "<アカウントID>:<ドメイン名>",
"cluster_uuid" : "qeqgYiNGTh6oBPEDkmfP2g",
"version" : {
"number" : "7.4.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "unknown",
"build_date" : "2020-05-05T04:47:22.951128Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
ここまで確認できたら、受信したメール用のインデックスを作成します。日本語を扱うため、kuromoji の analyzer を作成しています。<ドメインの VPC エンドポイント> の部分は、VPC エンドポイントに置き換えてください。
$ curl -X PUT -H "Content-Type: application/json" https://<ドメインの VPC エンドポイント>/mail -d'
{
"settings":{
"analysis":{
"tokenizer" : {
"kuromoji" : {
"type" : "kuromoji_tokenizer"
}
},
"analyzer" : {
"kuromoji" : {
"type" : "custom",
"tokenizer" : "kuromoji"
}
}
}
}
}'
成功すると、以下のようなレスポンスが返ります。
{"acknowledged":true,"shards_acknowledged":true,"index":"mail"}
次にインデックスのマッピングを設定します。今回のインデックスに含まれる property は、この 3 つです。
Property | 説明 | Data Type |
---|---|---|
subject | 件名 | text |
content | 本文 | text |
sentDate | 送信日時 | long |
$ curl -X PUT -H "Content-Type: application/json" https://<ドメインの VPC エンドポイント>/mail/_mapping -d'
{
"properties": {
"subject": {
"type": "text",
"analyzer": "kuromoji"
},
"content": {
"type": "text",
"analyzer": "kuromoji"
},
"sentDate": {
"type": "long"
}
}
}'
成功すると、以下のようなレスポンスが返ります。
{"acknowledged":true}
AWS Lambda 関数 (Document 追加用) の作成
次に、S3 にメールデータが保存された際に実行される Lambda 関数を作成していきます。この Lambda 関数では、メールデータと添付されている PDF ファイルを解析して、その内容を Elasticsearch Service に Document として追加する処理を行います。
ポリシーのフィルタに『AmazonS3ReadOnlyAccess』と入力し、表示されたポリシーのチェックボックスにチェックを入れます。最後に、『次のステップ: タグ』を押してください。
関数名を入力し、ランタイムに『Java 8』を選択します。次に、実行ロールには『既存のロールを使用する』を選んだ上で、上記で作成した Lambda 用のロールを選択します。最後に『関数の作成』を押してください。
まずは、関数コードをビルドしてアップロードします。サンプルコードの ZIP ファイルをダウンロードします。その後以下のようにコマンドを実行し、JAR ファイルを作成します。
unzip school-mail-search-lambda.zip
cd school-mail-search-lambda
mvn clean package
target ディレクトリの下に school-mail-search-1.0.jar というファイルができていれば成功です。
このコードでは、MIME 形式のメールデータや PDF を解析して、テキストを抜き出し、Elasticsearch Service ドメインにドキュメントとして追加しています。興味がある方はぜひコードをご覧ください。
次に、関数コードの下にある、環境変数の設定を行います。Lambda 関数のコードでは、Elasticsearch Service ドメインの VPC エンドポイントなどを環境変数として渡すようにしています。環境変数設定の『編集』か『環境変数を管理』を押してください。
環境変数は、以下のように設定してください。
キー | 値 |
---|---|
AES_ENDPOINT | Elasticsearch Service ドメインの VPC エンドポイント 例) vpc-xxxxxx-xxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com |
INDEX_NAME | |
REGION | Elasticsearch Service ドメインのリージョン 例) ap-northeast-1 |
ハンドラに『com.amazon.aws.buildersflash.schoolmailsearch.AddDocumentFunction::handle』を入力し、『保存』を押します。
最後に、この Lambda 関数から Elasticsearch Service ドメインが紐づく VPC にアクセスできるように、VPC の設定を行います。
EC2 コンソールを開き、Lambda 関数用のセキュリティグループを作成します。
セキュリティグループ名を入力し、Elasticsearch Service ドメインで設定した VPC を選択します。インバウンドルール、アウトバウンドルールはそのままで『セキュリティグループを作成』を押します。
VPC 接続で『カスタム VPC』を選択し、VPC には Elasticsearch Service ドメインで設定した VPC を選択します。サブネットには同じ VPC のプライベートな Subnet を選択してください。最後に、さきほど作成したセキュリティグループを選び、『保存』を押します。
これで Lambda 関数自体の設定は終わりです。では次に、メールを受信して S3 にメールデータが書き込まれた時に、この Lambda 関数が呼び出されるようにトリガーを設定します。
AWS Lambda 関数 (Document 追加用) の作成
さきほど作成した Document 追加用の Lambda 関数と同様に、もう一つ検索用の Lambda 関数を作成します。IAM ロールは、Document 追加用に作成したものを使います。
関数コード、環境変数、VPC には、追加用の Lambda 関数と同じものを設定してください。
異なるものとしては、基本設定のハンドラがあります。ハンドラには、『com.amazon.aws.buildersflash.schoolmailsearch.SearchDocumentFunction::handle』を指定します。
トリガーに API Gateway を選択し、『Create an API』で新たに作成するようにします。API type は『REST API』を選択します。セキュリティについては『オープン』にしていますが、メールを扱う API であることから、本来は何らかの認証を追加することをおすすめします。REST API のセキュリティについては、こちらを参考にしてください。
動作確認
では、いよいよ、メールを検索してみましょう。
再度、SES に設定したメールアドレスに対して、メールを送ってみてください。
今度は、S3 バケットにメールデータが書き込まれた後、Lambda 関数が実行され、Elasticsearch Service のインデックスが作成されます。
この URL を開いただけでは、クエリを指定していないので、何も表示されないはずです。ブラウザの URL に『*?query=検索文字列』といったパラメータを追加してみてください。検索文字列には、上記で送信したメールに含まれている文字列を指定してください。うまくヒットすると次のように JSON が返ってきます。
まとめ
うまく検索できましたでしょうか ? Elasticsearch Service の設定のために使用した EC2 を除き、AWS サービスを連携させることでサーバーレスなシステムを構築できることがお分りいただけたと思います。
使用しているサービスが多いため、手順が長くなっていますが、ぜひチャレンジしていただけるとうれしいです。
筆者プロフィール

福嶋 一史 (ふくしま かずふみ)
アマゾン ウェブ サービス ジャパン合同会社
プロトタイピング ソリューションアーキテクト
Web 系のソフトウェアエンジニアを経て、クラウドサポートエンジニアとして、2016 年に AWS 入社しました。その後、2019 年にプロトタイピングソリューションアーキテクトとなり、お客様のプロトタイピング開発を支援しています。好きな AWS サービスは、Amazon API Gateway と AWS Lambda です。
AWS を無料でお試しいただけます