Laravel + SendGrid Event Webhookでイベント発火を検知する

#1.はじめに
この前、[Laravel+SendGrid WebAPIでメール送信](https://qiita.com/m0nch1/items/e534fdaa795bbcc4c441)を書きました。

今回はWebAPI経由でメールを送信した時のイベントを受け取り、laravel側で処理する方法を書いていきます。

メール送信に失敗した時（存在しないメールアドレス宛てに送信されたときなど）に通知を受け取れるようにしていきます。

通常、POST先のURLは公開されている（SendGridからアクセスできる）ことが必須ですが、ngrokを使ってローカル環境でテストする方法も書いていきます。

#2.環境

- Laravel Framework 6.0.4
- "sendgrid/sendgrid": "^7.4"

#3.SendGrid Event Webhook
[公式ドキュメント](https://sendgrid.kke.co.jp/docs/API_Reference/Webhooks/event.html)に以下のように記載されている通り、Eventが発火した際にSendGridからPOSTを送信してくれます。

> SendGrid の Event Webhook は、SendGrid経由でメールを送信する際に発生するイベントを、指定したURLにPOSTすることができます。

> このデータの用途は、配信停止アドレスの削除、迷惑メール報告への対応、エンゲージできなかった受信アドレスの判定、バウンスされたメールアドレスの特定、メールプログラムの高度な分析などです。


#4.web.phpを編集
routes\web.phpにWebHook用のルーティングをPOSTメソッド追加します。
※念のため、GETリクエストはドキュメントルートにリダイレクトするように書いてます。

```php:web.php
Route::post('sendgrid/webhook', 'SendGridWebHookController@catchEvent')->name('sendgrid.catchevent');
Route::get('sendgrid/webhook', function () {
    return redirect('/');
});
```


#5.controllerを作成

コントローラーを作成します。

```bash
php artisan make:controller SendGridWebHookController
```

今回、コントローラーにはSendGridから送信されるPOSTのリクエスト内容をメールで通知するようにしています。

例えば、任意のLogファイルに通知内容を吐き出してそのLogを監視対象にするなど、さまざまな処理が考えられます。

**今回はイベントの種類によって処理を分岐したりはしていませんが、分岐が必要な場合、SendGridから送信されてくるリクエストの中身をログファイルに吐き出して確認するとよいと思います！**

※APIキーや送信元メールアドレスなどは.envに記載しています。

```php:SendGridWebHookController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Exception;
use Log;

class SendGridWebHookController extends Controller
{
    public function catchEvent(Request $request)
    {
        $notifications = json_encode($request->all());
        $notifications = json_decode($notifications);

        $email = new \SendGrid\Mail\Mail();
        $email->setFrom(getenv('FROM_EMAIL'), getenv('FROM_NAME'));
        $email->setSubject("メール送信エラー通知");
        $email->addTo('ここに送信先メールアドレス');

        $sendGrid = new \SendGrid(getenv('SENDGRID_API_KEY'));
        $email->addContent(
            "text/plain",
            strval(
                view(
                    'emails/templates/catchSendGridWebHook',
                    compact('notifications')
                )
            )
        );
        $email->addContent(
            "text/html",
            strval(
                view(
                    'emails/templates/catchSendGridWebHook',
                    compact('notifications')
                )
            )
        );
        try {
            $sendGrid->send($email);
            return true;
        } catch (Exception $e) {
            Log::debug($e->getMessage());
            return false;
        }
    }
}

```

一応bladeファイルも載せておきます！
bladeファイルの中では、イベントの種類ごとにメッセージを変えてみました。

```
<div>
    <p>
        メール送信に失敗しました。<br>
        以下詳細をご確認ください。
    </p>
    
    @foreach ($notifications as $notification)
    <div>
        <p>-------------------------------------------------------</p>
        <span>メールアドレス：{{ $notification->email }}</span>
    
            @if ($notification->event === 'dropped')
                <p>
                    次のような理由でメールがドロップされました。<br>
                    ・無効なSMTPAPIヘッダ<br>
                    ・迷惑メールコンテンツ（Spam Checker が有効な場合）<br>
                    ・配信停止されたメールアドレス<br>
                    ・バウンスされたメールアドレス<br>
                    ・迷惑メール報告されたメールアドレス<br>
                    ・無効なメールアドレス<br>
                    ・宛先アドレス数が通数上限超過
                </p>
            @elseif ($notification->event === 'deferred')
                <p>
                    受信側メールサーバから一時的に拒否されました。
                </p>
            @elseif ($notification->event === 'deferred')
                <p>
                    受信側メールサーバが受信できない、もしくは受け入れませんでした。
                </p>
            @else
                <p>
                    不明なエラーでメール送信に失敗しました。<br>
                    以下詳細を確認ください。
                </p>
            @endif
            <div>
                エラー詳細：<br>
                {{ json_encode($notification) }}
            </div>
    </div>
    @endforeach
  
</div>

```

#6.SendGrid側の設定

laravel側の準備ができたら、SendGrid側での設定をします。

SendGridのWebコンソールにログインしたら、Settings>Mail Settingsをクリックしましょう。

![SendGrid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174125/f5057f3f-eac5-61f1-0c89-133c4f50fcb6.png)

Event Notificationという設定項目があるので、ここを編集します。

HTTP POST URLにはlaravel側で作成したPOSTのルーティングを設定します。

![SendGrid (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174125/0a979bd3-5c66-4f5d-d774-ee9bd594591d.png)

SELECT ACTIONで発火対象のイベントを指定することができます。
今回は「Dropped」、「Deferred」、「Bounced」を指定しています。

URLを設定したら、青いボタンの「Tset Your Integration」をクリックしましょう。
SengGridからPOSTリクエストが送信され、Controllerに記述した処理が実行されれば成功です！

ログファイルに内容を飛ばしている場合は、tailfなどでログファイルを流しておくといいかもしれません。

#7.ngrokを使ってローカル環境でテストする

ngrokはlocalhost実行のサーバーを、一時的に外部に公開できるサービスです。
ばちくそ便利なのです。

以下の記事を参考にngrokをインストールしましょう。

[ngrokの使い方（windows, mac）](https://qiita.com/Esfahan/items/74e71edfe17d9935d47e)

#8.ngrokを起動してテスト

Windowsで実行する場合の例です。
ngrok.exeを実行すると、ターミナルが立ち上がります。

![Desktop screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174125/e68c4340-162c-fce1-fd01-eb29226dd4b6.png)

try typing ngrok.exe http 80 と記載があるように、公開したいPortを指定するだけで公開用のURLが発行されます！

例えば、laravelの場合php artisan serveで8000番ポートで起動し、以下のように実行すれば一時的に外部公開ができます。

![Desktop screenshot (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/174125/26b4b029-93eb-5521-fd55-184f0c2cba8d.png)


あとは、SendGridのWebコンソールでPOST先のURLを~~~.ngrok.ioに変更するだけです！

※ngrokを停止したいときはCtrl+Cで停止することができます。

#9.まとめ

ローカル環境でWebHookをテストしたいときはngrokが大活躍です！
いろんな通知を受け取って、WebHookを有効活用しましょう！！