【Laravel】オプトイン/オプトアウトのメール送信機能

こんにちは!フリーランス・エンジニアの 九保すこひ です。

さてさて、この間は コピペでOK!Laravelからwordpressに投稿する機能 などいくつかLaravelNotificationをつかった機能について記事を公開しました。

そして、Notificationとしてよく使われるのがメールかと思いますが、最近は送信内容によって「あること」に気をつけないといけなくなっています。

それは・・・

特定電子メール法

です。

つまり、メルマガなどを送信する際は、事前に同意をとらないといけないというルールです。(=オプトイン)

また、一度同意を得たとしても、その後受け取りを拒否された場合はメール送信してはいけないことにもなっていたりもします。(=オプトアウト)

そこで!

今回は、LaravelNotificationを使って、このオプトイン/オプトアウト機能を実装してみます。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Laravel 6.x

やりたいこと

今回開発する内容は次のとおりです。

  • 事前に許可したユーザーだけにメール送信
  • 送信されたメールには、クリックするだけで今後の受信を拒否できるオプトアウト・リンクを用意する

では実際にやってみましょう!

前提として

今回の記事は、Laravelのログイン機能がすでにインストールされていることを前提としています。

そのため、もしインストールがまだの方は以下を参考にして準備しておいてください。

usersテーブルにフィールドをつくる

まずはじめに、メール受信を同意しているかどうかがわかるようにemail_acceptedというフィールドをusersテーブルに作成します。

つまり、この項目がtrueならメール送信OK、falseならメール送信NGということになります。

では、以下のコマンドを実行してください。

php artisan make:migration add_email_accepted_to_users

すると、「database/migrations/****_**_**_******_add_email_accepted_to_users.php」というファイルが作成されるので、中身を以下のように変更してください。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddEmailAcceptedToUsers extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->boolean('email_accepted')
                ->default(false)
                ->after('email');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('email_accepted');
        });
    }
}

変更が完了したら、マイグレーションを実行しましょう。

php artisan migrate

すると、テーブルは次のようになります。

テストデータをつくる

今回はメール受信に同意しているユーザーとそうでないユーザーが必要になりますので、Seederでサクッとテストデータをつくっておきましょう。

以下のコマンドを実行してください。

php artisan make:seed UsersTableSeeder

すると、「database/seeds/UsersTableSeeder.php」というファイルが作成されるので、中身を以下のように変更します。

<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $names = [
            'taro' => '太郎',
            'jiro' => '次郎',
            'saburo' => '三郎',
            'shiro' => '四郎',
            'goro' => '五郎',
            'rokuro' => '六郎',
            'shichiro' => '七郎',
            'hachiro' => '八郎',
            'kuro' => '九郎'
        ];
        $email_accepted = true;

        foreach ($names as $name_en => $name_jp) {

            \App\User::create([
                'name' => $name_jp,
                'email' => $name_en .'@example.com',
                'password' => bcrypt('xxxxxxxx'),
                'email_accepted' => $email_accepted
            ]);

            $email_accepted = !$email_accepted;

        }
    }
}

変更が完了したら、このSeederを登録します。
database/seeds/DatabaseSeeder.php」を以下のようにしてください。

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
         $this->call(UsersTableSeeder::class);
    }
}

では、以下のコマンドでSeederを実行しましょう。

php artisan db:seed

すると、usersテーブルは次のようになります。

email_accepted1のものがメール送信しても大丈夫なユーザーで、0だと拒否です。

オプトアウト機能をつくる

続いて「クリックしたら今後はメール受信を拒否する」ためのURLを先につくっていきましょう。

仕組み

例えば、以下のようなURLをつくってオプトアウトしてもいいのですが、これだとなりすましでメール拒否されることも想定されるので、今回はオプトアウトするための「ワンタイムパスワード」を個別で管理して実装することにします。

http://*****/opt-out/(ユーザーID)

モデルとテーブルをつくる

以下のコマンドを実行してください。

php artisan OptOutTicket -m

するとモデルとマイグレーションのファイルがそれぞれ作成されるので、「database/migrations/****_**_**_******_create_opt_out_tickets_table.php」の中身を次のように変更してください。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOptOutTicketsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('opt_out_tickets', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('uuid');
            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('opt_out_tickets');
    }
}

ではマイグレーションを実行してテーブルを作成しましょう。

php artisan migrate

実行後、テーブルはこうなります。

ルートをつくる

続いて、オプトアウトするためのURLをつくります。
routes/web.php」に以下のルートを追加してください。

Route::get('opt_out/{uuid}', 'OptOutController@update')->name('opt_out');

コントローラーをつくる

先ほどルートで設定したコントローラーをつくります。
以下のコマンドを実行してください。

php artisan make:controller OptOutController

すると、「app/Http/Controllers/OptOutController.php」というファイルが作成されるので、中身を以下のように変更します。

<?php

namespace App\Http\Controllers;

use App\Notifications\NewsLetter;
use Illuminate\Http\Request;

class OptOutController extends Controller
{
    public function update($uuid) {

        $ticket = \App\OptOutTicket::where('uuid', $uuid)->first();

        if(is_null($ticket)) {

            abort(404);

        }

        // ユーザーの受信設定を「拒否」へ変更
        $user = \App\User::find($ticket->user_id);
        $user->email_accepted = false;
        $user->save();

        // オプトアウトのデータを削除
        $ticket->delete();

        return 'メール受信設定を「拒否」に変更しました。';

    }
}

※ 今回はメインがオプトイン/オプトアウトなのでビューは使わず単にテキストをreturnしています。

Notificationをつくる

ではやっと本題のNotificationでメール送信する部分をつくっていきます。
なお、今回はメールマガジンを送信することを想定して実装していきます。

まずは以下のコマンドでNewsLetterという名前のNotificationファイルを作成してください。

php artisan make:notification NewsLetter

すると、「app/Notifications/NewsLetter.php」というファイルが作成されるので、中身を次のように変更してください。

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;

class NewsLetter extends Notification
{
    private $view = '';

    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct($view)
    {
        $this->view = $view;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        if(!$notifiable->email_accepted) {  // メール受信を拒否している場合

            return [];

        }

        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return \Illuminate\Notifications\Messages\MailMessage
     */
    public function toMail($notifiable)
    {
        // オプトアウトのデータを作成する
        $ticket = new \App\OptOutTicket();
        $ticket->user_id = $notifiable->id;
        $ticket->uuid = Str::uuid();
        $ticket->save();

        return (new MailMessage)
            ->view($this->view, [
                'user' => $notifiable,
                'ticket' => $ticket
            ]);
    }

    /**
     * Get the array representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function toArray($notifiable)
    {
        return [
            //
        ];
    }
}

なお、この中で重要なのは次の3ヶ所です。

メール受信を拒否している場合はキャンセルする

まず、メール受信を拒否している(つまり、usersテーブルのemail_acceptedfalseの場合)はメール送信をキャンセルしなければいけませんが、やり方は以下のようにvia()の返り値を空白の配列にすることで実装します。

public function via($notifiable)
{
    if(!$notifiable->email_accepted) {

        return [];

    }

    return ['mail'];
}

メールを送信する部分

toMail()内でメール送信するコード自体は通常のNotificationと同じですが、重要なのは$notifiableが、このケースの場合はusersテーブルのデータになるという部分です。

そのため、$notifiable->idは、ユーザーIDを取得できますし、$notifiable->nameはユーザーの名前を取得することができます。

ビューを指定する部分

今回はメールマガジンを想定しているため、内容が毎回変わるという前提でコードを書きました。そのため、NewsLetterは以下のようにビューを指定する形で実行することになります。

new NewsLetter('emails.news_letter')

ビューをつくる

では、メールマガジンの内容をビューとしてつくりましょう。
resources/views/emails/news_letter.blade.php」というファイルを作成してください。

{{ $user->name }} さん、こんにちは。<br><br>

(メールマガジンの内容)<br>
(メールマガジンの内容)<br>
(メールマガジンの内容)<br><br>

<a href="{{ url('/your/event/url') }}">詳しい情報はこちら</a><br><br>

なお、今後同様のメールを受信しない場合は<a href="{{ route('opt_out', $ticket->uuid) }}">こちら</a>

テストしてみる

では、実際にメールを送信してみましょう!

うまくいっているかをチェックするために次のルートを「routes/web.php」に追加してください。

Route::get('opt_out_test', 'OptOutController@test');

そして、「app/Http/Controllers/OptOutController.php」にテスト用のメソッドtest()を追加します。

<?php

namespace App\Http\Controllers;

use App\Notifications\NewsLetter;
use Illuminate\Http\Request;

class OptOutController extends Controller
{
    // 省略

    public function test() {

        $users = \App\User::all();
        $view = 'emails.news_letter';
        \Notification::send($users, new NewsLetter($view));

    }
}

これで、「http://******/opt_out_test」にアクセスすると全てのユーザーに対してNotificationが実行されることになります。

なお、チェックする項目は以下の2つです。

  • メール受信に同意している人だけに送信できているか(拒否している人のメール送信をキャンセルできているか)
  • メールにかかれている「拒否リンク」をクリックしたらメール受信を拒否する設定にできるか

では、まずはテーブル内を確認してみましょう。
赤枠の人たちがメール受信に同意しているユーザーです。

では実際に「http://******/opt_out_test」にアクセスしてメール送信してみましょう!

送信された全メールです。

Toのメールアドレスを見てみると、受信に同意したユーザーだけにメール送信できていることが分かります😊✨

では、次に「太郎さん」に送信されたメールの中にある「拒否リンク」をクリックして、うまく処理ができるかチェックしてみましょう。

クリックすると、以下のような表示になりました。

では、念のためテーブルも確認してみましょう。

はい!「太郎さん」のemail_acceptedfalseになっています。

全てうまくいきました😊✨

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、ログイン機能のインストールなどはご自身で行ってください。

Laravel】オプトイン/オプトアウトのメール送信機能

おわりに

ということで、今回はLaravelを使って「オプトイン/オプトアウト」機能をつくってみました。

これを応用すれば、メールマガジンを送信する機能だけでなく、イベント情報のお知らせやバーゲンの通知などもできると思います。

ぜひみなさんのサイトでも活用してみてくださいね。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨ by 九保すこひ
このエントリーをはてなブックマークに追加       follow us in feedly