GAE に JSON 取得の Web サーバーを構築して SendGrid からメールする

Google Cloud Platform（以下、GCP）環境で、"Web サーバーへのリクエストからメール送信サービスを利用する"ということをしてみたかったので、色々確認してみました。

元々、AWS ばかり触っていて、AWS では AWS Lambda と Amazon SES 利用すれば同様のことができるというのはわかっていたのですが、今回は **GCP 環境で作業する**という縛りがあったので、「GCP でのメール送信サービスにあたるものは何か？」から「どうやって実装するのか？」を調べました。

調べると、Google App Engine（以下、GAE）と SendGrid を利用する方法が、[Google のドキュメント](https://cloud.google.com/appengine/docs/flexible/nodejs/sending-emails-with-sendgrid?hl=ja#top_of_page) で推されていたので試してみました。

※ 単純にやると、内容が Google ドキュメントとほぼ同じになりそうだったので、ドキュメントでは"フォームデータ"の POST を例にしていたのを、当記事では"JSON データ"での POST にしています。更に、ドキュメントでは"決まった内容のメール"しか送信できないので、JSON の内容に合わせて可変になるように変更しています。

# 概要

上記したように、"Web サーバーへのリクエストからメール送信サービスを利用する"ものを実装します。それを今回の GCP を関連のサービスを利用するという目的に当てはめ、、、

"GAE 上に Web サーバーを構築し、JSON データの POST リクエストを処理し、SendGrid ライブラリを利用して、メールを送信する"

<img width="797" alt="Screen Shot 2019-11-29 at 14.00.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/77d9c08b-2bc8-45e2-b507-9cf3a1cc23c9.png">


という形で構築します。

### Google App Engine とは

皆さんご存知かもしれませんが、知らない方もいるかもしれないので軽く説明しておきます。

今回利用する GAE は、Google のインフラ上でアプリケーションを作成し、実行できるようにする PaaS あたるものです。PaaS の特長の通り、プラットフォームを特にメンテナンスする必要なくサービスを構築できるという点で優れています。

ぶっちゃけ、今回目的としている内容は、Google Cloud Functions を使えば実現可能です。ただ特に HTTP 以外のイベント駆動も考えていなかったので、単純に GAE を使ってみました。（IaaS 系の Google Compute Engine 実現可能ですね！）

ちなみに、GAE を AWS サービスに当てはめると、AWS Elastic Beanstalk になるかと思います。Cloud Functions が AWS Lambda、Google Compute Engine が Amazon EC2 ですかね。（全く同じではないので対比するものでないと思いますが…）

### SendGrid とは

GCP 内に存在するサービスでなく、サードパーティのメール送信サービスです。毎月 12,000 件のメールを GCP ユーザーにトライアルとして無料で提供してくれています。また、SendGrid を使用すると、メールの到達率が向上し、アプリから送信されたメールの状況をより明確に把握することができるようです。

<img width="1517" alt="Screen Shot 2019-11-29 at 11.40.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/744e29c8-86b6-69bb-196d-068139deb619.png">


上図のような SendGrid インターフェースまたは API を介して、開封数、クリック数、登録解除、スパムレポートなどに関する統計情報を確認することが可能です。

今回は "SendGrid" を選択しましたが、Google のドキュメントでは "Mailgun"、"Mailjet"も候補に挙がっていました。

サードパーティのサービスなので、GCP 以外の環境からでも SendGrid は利用できます。

#実装

基本的には、チュートリアル「[インスタンスからのメール送信](https://cloud.google.com/appengine/docs/flexible/nodejs/sending-emails-with-sendgrid?hl=ja#top_of_page)」に従って実装しています。上記したように少し内容を変え、2019/11/27 時点で動くものとして、手順を記載していきます。

1. GCP プロジェクトを作成
2. Marketplace より"SendGrid Email API"の無料プランを開始
3. SendGrid から API Key を取得
4. GAE へ Web サーバーをデプロイ
5. JSON データを POST 送信

## 1. GCP プロジェクトを作成

[GCP](https://console.cloud.google.com/) にアクセスし、プロジェクトを作成します。後に GAE を使用するのと、SendGrid Email API を有効化するのに必要になるので、プロジェクトに対して課金設定をしておきます。

## 2. Marketplace より "SendGrid Email API" の無料プランを開始

Marketplace タグで "SendGrid"と検索し、"SendGrid Email API" のページを開きます。
"無料プランを開始"より SendGrid Email API を有効化します。

私はここで若干ハマったので注意点を下記しておきます。。。

まずプロジェクトの課金設定が行われていないと"無料プランを開始"ボタンが、グレーアウトされた状態になります。
更に課金設定を行っていても、"SendGrid Email API" を有効化するアカウントに課金設定の操作が可能な権限がないと同じく"無料プランを開始"ボタンがグレーアウトされた状態になります。
<img width="967" alt="Screen Shot 2019-11-29 at 11.46.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/f001201f-36a6-26c2-993d-8e1491e13d64.png">

GCP での課金をグループで管理していて、自身が課金設定の管理者でない場合、上記のような状態になり得るので注意が必要です。課金設定の管理者に、プロジェクトの課金有効化と、Marketplace での "Sendgrid Email API" の有効化をセットでお願いしましょう。

## 3. Sendgrid から API Key を取得

Marketplace の Sendgrid Email API ページで、"無料プランを開始"をクリックした後、Subscribe 項目で利用プランの選択を、Activate 項目で SendGrid アカウントの登録を行います。そうするといつもの Google 認可画面が出てくるので、許可をクリックします。
以上で、SendGrid Email API の有効化が完了です。完了後、切り替わる画面上に "SendGrid ウェブサイトで API キーを管理する"ボタンがあるのでクリックすると、SendGrid のウェブサイトに遷移します。

SendGrid ウェブサイトの "Settings" から、"API Keys"タブを開き、"Create API Key" ボタンをクリックすると API Key が発行されるので文字列をコピーしておきます。

## 4. GAE へ Web サーバーをデプロイ

App Engine を開きます。プロジェクトの課金が有効になっていないと操作できないので、課金設定を有効にしておく必要があります。

今回は Nodejs のバージョン v10.10 で作成していきます。
GAE デプロイのため、下記ファイルを作成します。

- app.flexible.yaml（GAE を構成するファイル）
- app.js（GAE 上で動く処理ファイル）

加えて、Package.json の修正も必要になります。

まず、app.flexible.yaml からです。

```javascript:app.flexible.yaml
runtime: nodejs10
env_variables:
  SENDGRID_API_KEY: <your-sendgrid-api-key>
  SENDGRID_SENDER: <your-sendgrid-sender>
```

"env_variables" には、app.js が処理で利用する環境変数を記載します。
"SENDGRID_API_KEY" は、SendGrid ウェブサイトで取得した API Key を、"SENDGRID_SENDER" は、送り元となるメールアドレスを記載します。

app.js ファイルに、GAE での処理を記載していきます。

```javascript:app.js
'use strict';

const express = require('express');
const bodyParser = require('body-parser');

const { SENDGRID_API_KEY } = process.env;
const { SENDGRID_SENDER } = process.env;
const Sendgrid = require('@sendgrid/client');

Sendgrid.setApiKey(SENDGRID_API_KEY);

const app = express();

app.use(bodyParser.json());

app.post('/hello', async (req, res, next) => {
   console.log(req.body);
   const request = {
      method: 'POST',
      url: '/v3/mail/send',
      body: {
         personalizations: [
            {
               to: [{ email: req.body.email }],
               subject: req.body.sub,
            },
         ],
         from: { email: SENDGRID_SENDER },
         content: [
            {
               type: 'text/plain',
               value: req.body.text,
            },
         ],
      },
   };

   try {
      await Sendgrid.request(request);
   } catch (err) {
      next(err);
      return;
   }

   res.send('POST is sended.');
});

if (module === require.main) {
   const PORT = process.env.PORT || 8080;
   app.listen(PORT, () => {
      console.log(`App listening on port ${PORT}`);
      console.log('Press Ctrl+C to quit.');
   });
}

module.exports = app;
```

上記 app.js を実行するには、下記のモジュールを追加しておく必要があります。

```
npm install --save express
npm install --save body-parser
npm install --save @sendgrid/client
```

"app.use(bodyParser.json());" を記載することで、JSON データでの POST 送信を受け取れるようにし、key が "email"、"sub"、"text" で送られてきた値を、メールの"送信先"、"件名"、"本文"内容にあてています。

また、package.json も修正が必要で、あらかじめ登録してある内容に下記コードを追加する必要があります。

```json:package.json(追加)
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  }
```

以上、完成したら下記コマンドで GAE にデプロイを行います。

```
gcloud app deploy app.flexible.yaml
```

今回は、yaml ファイルに "flexible" と付けているので、ファイル名指定してデプロイしてください。

※ gcloud コマンドに関しては、[Cloud SDK](https://cloud.google.com/sdk/?hl=ja) を参照してください。

デプロイが完了したら、いよいよリクエストの送信です。

## 5. JSON データを POST 送信

GAE の Web サーバーに対して、JSON データを POST します。

GAE がデプロイされると、"appspot.com" の URL が発行されます。今回 POST のエンドポイントを "/hello" と指定したので、下記の内容で POST 送信を行います。


```json
エンドポイント
https://sendgridtestXXXXXXXX.appspot.com/hello

メソッド
POST

JSON データ
{
  "email":"dokiXXXnjc.co.jp",
  "sub":"テスト",
  "text":"テストメールです"
}
```

Chrome 拡張の送信ツールでも、curl でもなんでもいいので上記内容を送信します。私は "Postman" を使いました。

<img width="920" alt="Screen Shot 2019-11-28 at 17.28.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/b6ba39d5-c688-d0e1-e72e-e4ce46e4dea7.png">

自分から自分にメールを送信しているので、結果下記のようにメール送信が行われました。

<img width="352" alt="Screen Shot 2019-11-28 at 17.28.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/929734d8-bca8-a3a6-f052-af3c8d873955.png">

yaml ファイルの環境変数に設定した "dokiXXXnjc.co.jp" さんから、JSON の email に設定した"dokiXXXnjc.co.jp（To自分）"さんに送られています。

# まとめ

SendGrid のライブラリがあるので、実装自体簡単にできますね！

SendGrid では、リアルタイム情報の取得が可能で、メールの送信だけでなく、メールの受信、webhook を使用して送信したメールの確認を行うことができます。機能は API でも用意されているので、"メール開封率の確認"や"メール受信をトリガーとして処理実行"などを他のシステムに組み込み、統計取得やその可視化などに利用できそうです。

[Event API ](https://sendgrid.com/docs/for-developers/tracking-events/event/)、[Inbound Parse API ](https://sendgrid.com/docs/for-developers/parsing-email/inbound-email/)

SendGrid と Amazon SES とを軽く比べてみると（単純に比較できるものではないですが）、1 通あたりの値段は SendGrid の方が高いですね（無料で利用できる範囲も Amazon SES が 60,000 と多い）。ただ SendGrid には、メール開封の確認ができるといったことがデフォルトでできるという強みがあるようです。

メール配信サービスに関する良い比較サイトがあったので参照して確認してみてください。
[SocialCompare - Best Transactional Email Service Comparison](http://socialcompare.com/en/comparison/transactional-emailing-providers-mailjet-sendgrid-critsend)