SendGridのBounceへの気づきと対応

# いきさつ
Aさん「katahiroさん、作ってもらったツールで通知メールを送ってもらっていますが、一部のグループアドレスに届いていないそうです」
katahiro「ぇ、前もそんなこと言われてデバッグコード埋め込んで、 `SendGrid` で送信した時のレスポンスコードを毎回確認して 202 で応答されているから送ってはいると思うんですが・・・」
Aさん「他のグループアドレスには届いているのですが、あるグループアドレスだけ届かなくて」
katahiro「確認してみますね」

# 確認してみよう
組織に提供しているツールは `Slack` のBotとして稼働しており、Node.jsで動かしています。
Node.jsでSendGridのSDKを利用してメール送信しているのですが、以前もうまく配信されない、という話を受けていたので添付のようにメール送信のライブラリを呼び出すたびにSlackで私自身に通知が来るようにしています。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b53bf0f6-fbba-47a1-5cef-808fe6cf3ae7.png)
見てる限りは応答ステータスが202になっているので、SendGridの送信キューには入っているはず、どうして届いてないんだろう？？グループアドレスは全部同じドメインなので、違いがよくわからず。。。

## SendGridのポータルを見てみよう
IBM CloudでSendGridのサービスをオーダーしているので、IBM CloudのポータルからVendorポータルの表示でSendGridのポータルにアクセスします。
Global Statsのページを見てると赤い線がちらっと見える。ナニコレ？この赤っぽい線だけ表示してみよう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/467849f8-b125-4b05-5ede-e930040ca67a.png)
### **Bounces : メールの不達**
### **Bounce Drops : メールの不達リストに含まれるので破棄されたメール**

ゎぉ。。。届いてないのは正しかったのか。

SendGridのポータルで左側にあるSupressionsの下にあるBounceからBounceとして扱われているメールアドレスの情報が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/810453d9-55cb-23ae-76f9-138fe4def651.png)

さて添付画像は空ですが、実際に開いたときはメールが届かないといわれているグループアドレス(group-address@test.mail.com)が一覧に入っていました。
もちろん気まぐれにBounceとして扱われるわけではなく、SendGridとして理由があってBounceに登録される形になります。
さて、その理由を確認すると・・・

:::note alert
User HogeHoge (HogeHoge@test.mail.com) not listed in xxx Directory
:::
ぇ、グループアドレスに含まれるメールアドレスの1つが未達だったせいで、グループアドレス自体がBounce扱いになっちゃうんだ。。。(厳しくない？)
おそらく、グループアドレスの中に退職された方が含まれてたのかなぁと推測。
何はともあれ対応を考えてみましょう。

# Bounceに気づくために
一度Bounceの対象となるとSendGridでは、以後の配信メールの宛先にBounceになったメールアドレスが含まれてると、その宛先はDrop(破棄)される形になります。
なので、何はともあれBounceが発生したら気づきたいところです。
それに対してSendGridでは、添付のようにBounceが発生すると指定されたメールアドレスに通知してくれる仕組みがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/b81ebebf-7282-4877-b2ea-bd8b3f1b0fdb.png)

んー、でもこれではアプリ屋さんっぽくない。。。

# Bounceに気づくアプリを作ってみよう
SendGridでは、発生したEvent情報をWebhookしてくれる機能があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/c1365484-521b-77e5-f71b-575a3e14e705.png)

じゃあEvent情報がWebhookされると、せっかくなのでSlackにその情報が書き込まれるようにしよう。
とりあえず手っ取り早く `express` のフレームワークを使って、さくっと作ってしまいましょう。
`express-generator`を使ってテンプレートを作って、SendGridから呼び出される経路のRouterだけ作ったら良いかな。

```Javascript:sgwebhook.js
require('dotenv').config();
var express = require('express');
var router = express.Router();
const { WebClient } = require('@slack/web-api');
const slackToken = process.env.SLACK_TOKEN;
const slackChannel = process.env.SLACK_CHANNEL;
const client = new WebClient(slackToken);

/* GET home page. */
router.get('/', function(req, res, next) {
    res.send('respond with a resource');
});

router.post('/', function(req, res, next) {
  res.send(req.body);
  if(req.body.length > 0){
        const eventsArray = req.body;
        eventsArray.forEach(event => {
            const email = event.email;
            const hookevent = event.event;
            const reason = event.reason ? event.reason : 'UNKNOWN';
            const text = ` event: ${hookevent}\n email: ${email} \n reason: ${reason}`
            client.chat.postMessage({
                channel: slackChannel,
                text: text,
                icon_emoji: ':email:',
                username: 'SendGrid Event',
            });
        })
  }
});
```
WebhookのリクエストのBodyに配列でイベント情報が入るので、その配列の数だけループ。
イベントも配信したといった Deliberd イベントの場合、reasonが配列で渡されるオブジェクトに含まれていないので注意。
SlackのBot用のTokenがあるので、Slackでメッセージを送るように設定して完成。
※処理はPOSTメソッドに記述、GETメソッドはなくて良いけれど、アプリの動作確認で利用してます。

# アプリをどこで公開するか
アプリのコードを書くのはさくっと終わったし、他で使ってる `Dockerfile` やら `docker-compose.yaml` をコピーしてきて、コンテナ化まで完了。
あとは、なるべく費用のかからない環境で動作させたいところ。

# よし、Code Engineを使おう
個人的に最近コンテナ化したアプリをIBM Cloud上で簡単に立ち上げるのでよく使ってます。
課金要素は以下かな？
- 受け付けた月間http(s)リクエスト数
- アプリが稼働したのに使用した月間vCPU秒
- アプリが稼働したのに使用した月間GB RAM秒
- ソースコードからコンテナ作成する場合のコンパイルマシン環境リソース費用

vCPU秒って何？という感じもすると思いますが、コンテナの稼働環境を例えば、0.5vCPU 2GB RAMとCode Engineで定義します。
この環境、アプリが動くと1秒ごとに、0.5vCPU秒が発生しているので、例えばこの環境を1時間動かすと 0.5vCPU × 3600秒 = 1800vCPU秒 消費した計算になります。

さて、このアプリWebhookで呼び出された時だけ動けば良いので、常時コンテナが動いている必要は無いです。
Code Engineの良いところはゼロスケール、つまり実行されるまでコンテナを休止させておくことが可能です。

つまり
- SendGridのWebhookで呼び出された時しか動作しないので、vCPU秒やGB RAM秒はあまり使用しない
- 同じ理由でhttpのリクエスト数も極小
- 自分の環境でコンテナ化してDocker Hub経由で実行させているので、コンパイルマシンの費用もかからない

一応Code Engineとしては月に10万vCPU秒、20万GB RAM秒、10万httpリクエストまでは無償で提供してます。
つまり、今回の利用はタダ！(SendGridからのWebhookで呼び出される回数は月に30回程度)

# 公開してみた
Code Engineで公開するとこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/28c6fb29-2eea-776a-9774-9a72769d450e.png)

右上のTest Applicationからコンテナのアプリにアクセスできます。

ゼロスケールにしてるので、最初にアクセスすると20秒くらい表示に時間かかりますが、今回expressのフレームワーク上に `sgwebhook` でアクセスするRouterを構成したので、目的のURLにブラウザでアクセスすると、先ほどのGET要求用に作成されたロジックがうまく呼び出されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/bf9f3e96-591e-be35-3946-405c7eeaf4a7.png)

# SendGrid側の設定
SendGridのWebhookを設定します。
先ほどCode Engineで公開したURLをPost URLに貼り忘れないようにだけしてください。
あとは、Bouncedも含めて、通知を受けたいイベントのチェックを入れて作成しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/04c3f005-35e7-5eb1-ad7d-7e61a62f1789.png)

# あとは設定したSlackに通知が送られてくるのを待つ
ｷﾀ―――(ﾟ∀ﾟ)―――― !!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/67b5e032-8178-2da9-625a-1cf77f39281c.png)
毎日動くものではないので、いつ起動するか未知数でしたが、設定した日の夜に動きました。
いったんBounceされているメールアドレスはすべてクリアしていたのですが、グループアドレスの配信先の調整はしないままだったため、同じ理由で再度Bounceされていることが通知でわかりました。

ちなみに、一度Bounceに入るとそれ以降はDropされることも通知でわかることができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/195e9911-f079-788f-2b3a-d8e94b1795a0.png)

とりあえずBounceが発生したらわかるので、これはこれで良いかな。
後はBounceが発生した場合に、BouceのリストをクリアするようにSendGridのAPIを叩くようにするかですね。
このあたりを実装するかは、これから考えます。

# おわりに
SendGridのサービスを利用してプログラムでメール配信している場合、多くがSendGridのSDKを利用してメール送信しているのではないかと思いますが、SDKの中でのメール送信のステータスが正常であったとしても送信できていないケースがある、というのを初めて知った(体感した)ので、記事にさせて頂きました。

同じような内容で悩む人が一人でも減れば幸いです。