【Laravel5.5】メール送信にホワイトリストチェックをいれるよ


# まえがき
開発環境とかはMailHogをつかって実際に送信しないようにするけど、
ステージングや検証環境みたいな、
　**自分や自社宛のだけは実際に送信したい**
てときにホワイトリストを作って判別することがあります。
で、先日もLaravelを使っててそんな状況になったので、対応のメモです。



## ツール
- XAMPP v3.2.4
    - PHP 7.3.8
        - Laravel 5.5.45

## メール送信処理

今回は、 `Illuminate\Mail\Mailable` を使ってテンプレートを読み込んで送信する方法で実装しています。

```php:App\Notifications\InvoicePaid
use App\Mail\InvoicePaid as InvoicePaidMail;

class InvoicePaid extends Notification
{
    // 省略

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        return ((new InvoicePaidMail())->build())
            ->to($notifiable->email);
    }

    // 省略
}
```

```php:App\Mail:InvoicePaid
    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->text('mail.invoice_paid');
    }
```

別にMailを挟まなくてもテンプレートって読み込めるんですかね････？
よく分かりません(´・ω・｀)



# 実装

メール送信時、いくつかのイベントが呼び出されていますが、そのうち送信前のイベントは `NotificationSending` です。
`NotificationSending` が呼ばれているのはここ。

```php:Illuminate\Notifications\NotificationSender
    /**
     * Determines if the notification can be sent.
     *
     * @param  mixed  $notifiable
     * @param  mixed  $notification
     * @param  string  $channel
     * @return bool
     */
    protected function shouldSendNotification($notifiable, $notification, $channel)
    {
        return $this->events->until(
            new Events\NotificationSending($notifiable, $notification, $channel)
        ) !== false;
    }
```

「通知が送信可能かを判別」と、望んだままのイベントみたい。

`NotificationSender` にあるから、別にメール以外でも出来るんですかね？
そうだったら軽いタイトル詐欺になるのかしら････(;´･ω･)

ともかく、このイベントをつかんでホワイトリストにかけるので、リスナーを登録。

```php:App\Providers\EventServiceProvider
    protected $listen = [
        'App\Events\Event' => [
            'App\Listeners\EventListener',
        ],
        'Illuminate\Notifications\Events\NotificationSending' => [
            'App\Listeners\CanSendMailListener'
        ]
    ];
```

あとは定義した `CanSendMailListener` を用意。

```php:App\Listeners\CanSendMailListener
<?php

namespace App\Listeners;

use Illuminate\Container\Container;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Events\NotificationSending;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\Event;

class CanSendMailListener
{
    /**
     * メールアドレスのホワイトリスト
     * @var array
     */
    const WHITE_DOMAINS = [
        '@example.com',
    ];

    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  Event  $event
     * @return void
     */
    public function handle(NotificationSending $event)
    {
        if (!\App::environment('staging')) {
            return;
        }

        $mailable = $event->notification->toMail($event->notifiable)->build();
        if (!$this->canSendToAddress($mailable->to[0]['address'])) {
            // 送信対象外なのでログに出力
            $mail_body = Container::getInstance()->make('mailer')->render(
                $mailable->textView, $mailable->buildViewData() ?? []
            );

            \Log::info('this email not exist white_list. not send.');
            \Log::info('to: '.json_encode($mailable->to));
            \Log::info('body: '.$mail_body);

            return false;
        }

        return true;
    }

    /**
     * 送信可能なメールアドレスか
     *
     * @access private
     * @param  string  $email
     * @return bool
     */
    private function canSendToAddress($email)
    {
        foreach (self::WHITE_DOMAINS as $domain) {
            if (strpos($email, $domain) !== false) {
                return true;
            }
        }
        return false;
    }
}
```

ステージング環境だけでこの処理をやりたいので、それ以外はスルーさせてます。

また、送信しない場合は、実際のメール本文の内容やらなんやらを確認したいかもなので、
ログへ出力するようにしています。
なお、今回はテキストメールだけだったので、メール本文の取得は決め打ちにしています。(´・ω・｀)
HTMLメールの対応が必要な場合は、各自でもうちょいなんとかしてください(^_^;)
（もし自分でなんとかする案件があったら追記するかも。。。）

これを入れたうえで、実際にメール送信できるよう各種設定をいれれば完了。

# あとがき
「ステージング環境はメールとばない、つらい('A`)」という共通認識がプロジェクトであったんですが、
とはいえ毎回ログを調べさせられるのもメンドいので、急きょ用意したのがコレ。
軽く確認して問題はでていないものの、いまだおっかなびっくりしてます(;´∀｀)

あと、これ書いてて気がついたんですが、
判定自体の可否とか、ホワイトリストは `.env` とかに出して環境ごとに切り替えられるようにしてもいいかもしんないです。
もしかしたらステージング環境以外にお客さん公開用環境とか、審査用環境とか、似たようなのがあるかもしれないし････。
まぁ、自分のときはなかったので、別にいっかなってσ(^_^;)