コード自動生成とDIを活用したレバレッジ指向なメールプレビュー機能を実装しました

はじめに

こんにちは、エンジニアの津崎(@820zacky)です。 今回は、先日実装したLaravelでのメールのプレビュー表示、テスト送信機能についてご紹介します。

「メールプレビューはあると便利そうだけど、 メールごとにプレビューのための処理を追加する必要があるんじゃない? すでにたくさんの種類のメールがあるし、メールを追加するのも大変になるからやりたくないよ」 と思ったエンジニアのあなた。

まさにその通りです。私たちも同じ気持ちだったので、最小限の労力でメールプレビューができる仕組みを目指して機能を作りました。

私たちが開発・運用しているマッチングプラットフォームでは、ユーザへの通知やメールマガジンなど様々な場面でメールを送信しており、現在110種類ものメールが存在します。

これらのメールは、開発時にPdMへ内容の確認を行ったり、営業などの他部署から内容の問い合わせを受けることがあります。 プレビュー機能を実装する前は、メールの内容を確認するには、開発環境で実際にメール送信の条件を作り、メール送信を行うことで確認を行っていました。

開発環境ではMailtrapというメールのサンドボックスサービスを利用しており、開発環境から送信されたメールはMailtrap上のメールボックスにて表示の確認ができるようになっております。

Mailtrap - Email Testing Tool #1

f:id:zacky2:20210514180659p:plain
Mailtrap上の受信箱にて受信したメールを開いているスクリーンショット

Mailtrapにより、送信されたメールについては簡単に確認と共有ができるのですが、メールによってはバッチを実行しないと送信されなかったり、メール送信の条件が難しいものがあり、メール送信することが手間のかかる作業となっていました。 そこで、マッチングプラットフォームの管理画面に、メールの内容を簡単に確認するための機能としてメールプレビュー機能を実装しました。 また、メールクライアントによる表示の崩れがないか確認できるよう、メールの送信機能も併せて実装しています。 スマートフォンの実機でのメール表示や、Outlookなど癖の強いメールクライアントなどもあるため、凝ったデザインのメールを作る際は必須の機能です。

どんな機能か?

社内のメンバのみアクセスできる管理画面から利用できる機能です。 メールの一覧が並んでおり、「プレビュー」と「任意のメールアドレスに向けたメール送信」ができます。

f:id:zacky2:20210514184109p:plain
メールプレビュー機能のメール一覧画面

f:id:zacky2:20210514204030p:plain
メールのプレビュー画面

実装の詳細

実装の詳細についてはQiitaの記事をご覧ください。

qiita.com

メール一覧の取得

メールプレビュー機能を作るにあたって、プロダクトで使われているメールクラスの一覧が必要です。 しかし、新しいメールを追加するたびに、メールクラスのパスをリストに追加するのは億劫な作業であり、追加の漏れも懸念されます。 メールプレビューのためにメール実装の手間が増えるのは避けたいところです。 そこで、実行するだけでメールクラスの一覧を持つクラスを自動生成するコマンドを作りました。

f:id:zacky2:20210514201820p:plain
メールクラスリスト生成コマンドの実行例

メールクラスの一覧の取得する方法として、特定のディレクトリを探索しメールのクラスが記述されたphpファイルの抽出を行っています。 メールクラスはMailableを継承したクラスであるため、ReflectionClassを使い、再起的にIlluminate\Mail\Mailableを継承しているかチェックすることで判別できます。

ファイル探索と継承のチェック処理はページ表示のたびに行うと処理に時間がかかってしまうと想定できたため、コマンド化し手動実行するようにしました。

DIを活用したメールプレビューの仕組み

LaravelのControllerクラスは、メール(Mailable)のインスタンスを返却するとHTMLとしてレンダリングしてくれます。 そのため、Mailableクラスのインスタンスを作ることができれば簡単にプレビューを行うことができます。

しかし、メールのインスタンスは簡単に作ることはできません。 なぜなら、メールは送信先が可変であったり、本文が可変であるものがほとんどであり、可変な部分はインスタンス生成の引数として渡してあげる必要があるためです。

<?php
use Illuminate\Mail\Mailable;

class HogeMail extends Mailable
{
    private User $user;

    // 例として引数にUserを渡している。インスタンス化するにはUserのインスタンスが必要。
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function build()
    {
        return $this
            ->subject( $user->getName() . "さんこんにちは!") // 対象のユーザによってタイトルを変える
            ->to($user->getEmail())  // 対象のユーザによって送信先を変える
            ->markdown('emails.hoge_mail');  // 対象のユーザによって本文を変える (Markdownの外部ファイルにて記述)
    }
}

実際のプロダクトでは100種類以上のメールが存在するため、それぞれのメールについて引数に渡す値を作り、Mailableクラスのインスタンスを生成するのは手間のかかる作業です。 メールを増やすたびに毎度メールプレビューのための処理を追加しなければならないのは、開発チームのバリュー「レバレッジ指向」と乖離します。

tech.macloud.jp

そこで、LaravelのDI機能を使って、メールに渡す引数についてDIするように実装を行いました。DIを活用すれば、同じ種類の引数を使っているメールであれば、処理を追加しなくてもメールのインスタンス化ができ、プレビューを表示することができます。

メールの引数のインスタンス化(ダミーの作成)については、すでにUnitテスト用にダミーのインスタンスを生成する実装が存在していたため簡単に作ることができました。

しかしながら、全てのメールについて引数を作ることは大変です。そこで、最初の実装ではいくつかのメールだけがプレビューできれば良く、段階的にプレビューできる対象を増やしていくことにしました。 メールで使われる引数はたくさんの種類がありますが、共通している引数も多いです。 よく使われるいくつかの引数についてDIを行うことで 30% 程度のメールについてプレビューできるようになりました。

f:id:zacky2:20210514192654p:plain
DIできてないメールはインスタンス化できないためプレビューできない

プレビュー未対応のメールについては、要望があった時に対応予定です。DIを追加するだけなので大きな手間はかかりません。

まとめ

今回、最小限の労力でメールのプレビューが実装できる仕組みを作りました。 一つはファイル探索とRefrectionを利用したメールクラスリストの自動生成、もう一つは、DIを利用したメールインスタンスの生成です。 「最小限の労力で大きな成果を出す」という私たちのバリューの一つである「レバレッジ指向」が発揮されたいい機能開発ができたと思います。

最後に

M&Aクラウドではエンジニアを絶賛募集中ですので、興味がありましたら是非以下からご応募ください!

www.wantedly.com

www.wantedly.com