LaravelでメールをDKIM署名」で作成したメールのDKIM署名が正しいかどうかを判断するには、Gmailアプリなら受信したメールの「メッセージのソースの表示」でDKIMがPASSあるいはFAILで判定が表示されるのでわかります。しかし、ユニットテストでそれと同様な判断するにはどうしたらよいのか、というのが今回のお話です。

メールのDKIM署名をチェックするツール

さきに話したようにGmailでは、以下のように「メッセージのソースの表示」でソースを見ると、DKIMの署名が正しいならPASSと出ます。

しかし、ユニットテストでDKIM署名のチェックするには、配信したメールをファイルに落としてそれをチェックできるコマンドが必要です。

運よくそのツールを見つけることができました。ツールは残念ながらphpではなくpythonで書かれたものですが、以下の実行でインストールできます。

$ pip install dkimpy

このコマンドを利用するとDKIM署名のチェックの実行はとても簡単です。先ほどのGmailからダウンロードしたファイルをtest.emlとしてチェックしてみましょう。

$ dkimverify < test.eml
signature ok

署名OKです!

テストしたメールの送り元(From)はhello@lotsofbytes.comで、lotsofbytes.comのドメインには前回説明したようにAmazonのRoute53のDNSサーバーにTXTに公開鍵のエントリーが登録されています。このツールはそれを取得してメールのDKIM署名が正しいかどうかチェックしてくれます。

配信するメールをファイルに落とす

DKIM署名の正負を判断してくれるコマンドを見つけたところで、こんどはそのコマンドにフィードするためのメールデータが必要です。メールを作成するのは前回に開発したDKIM署名処理を行うMailableなので、それで生成されたメールのデータをファイルに落とす必要あります。しかし、Mailableから直接出力できるメソッドがあれば良いのですが、Mailableを使用するメーラー(smtpとかの)との関わりのため、そのようなメソッドはMailableには存在しません。

そこで考えるのは、メーラーをログとして、つまり.envMAIL_MAILER=logと設定してファイルに落とすことです。しかし、ファイルはデフォルトではlaravel.logとなるし、出力はログなので記録時の日時の余計な文字列が含まれ、加工が必要となります。

ちなみに、ユニットテストの設定のXMLファイルでは、以下のようにメーラーは配列に落とすのがデフォルトとなっています。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
...
    <php>
...
        <env name="MAIL_MAILER" value="array"/>
...
    </php>
</phpunit>

ということは、メールの送信のテストがあれば、メモリにその内容が入ることになります。つまり、メール配信後にメモリから送信した内容を取得すればよいのです。

これは意外に簡単で、通常のメール送のコードにtoString()の文字列化のメソッドを追加するだけです。以下、tinkerで見てみましょう。

まず、ユニットテストのようにメーラーを配列とします。

> config(['mail.default' => 'array']);
= null

宛先を変数として、

> use App\Mail\Newsletter;
> $email = 'test@example.com';
= "test@example.com"

NewsletterのMailableを初期化してMailのファサードで送信すると、

>  Mail::to($email)->send(new Newsletter($email))->toString();

DKIM署名のメールのデータが返ってきます。

= """
  From: Mailable Test <hello@lotsofbytes.com>\r\n
  To: test@example.com\r\n
  Subject: =?utf-8?Q?=E3=83=A1=E3=83=AB=E3=83=9E=E3=82=AC?=\r\n
   =?utf-8?Q?=E3=81=AE=E9=85=8D=E4=BF=A1=E3=81=A7=E3=81=99?=\r\n
  MIME-Version: 1.0\r\n
  Date: Sun, 04 Feb 2024 21:58:58 +0000\r\n
  Message-ID: <bfe853f9bd87f57ab1f02e48f3465bd9@lotsofbytes.com>\r\n
  DKIM-Signature: v=1; q=dns/txt; a=rsa-sha256;\r\n
   bh=Ueb0qJ5i58RAOT361r7NWPIoj8dj5uSTjaEHFZSj/p8=; d=lotsofbytes.com; h=From:\r\n
   To: Subject: MIME-Version: Date: Message-ID; i=@lotsofbytes.com; s=default;\r\n
   t=1707083938; c=relaxed/relaxed;\r\n
   b=EiklbRI4G7Q8VpIM4LAXfyCJKJveXZQMdRza/Q1g/XyAG0wEp5HfXa33RnylIB27C1gozstjp\r\n
   InPV6cqTrWI1SF0xQaBjknHqVIR+eh/Eh+zEidYgmP4Ocs2QC/6iAtx1UpCvgIk4AnvgM/y3q\r\n
   RW+3DkVmIu79nGSRzBtwG3BlG5iA1Eo4NGZt//k70Qi8Ngkl33ce0VeyYZ5EzHVokupnzJiB3\r\n
   yZeWtUEBERog12/kKeLYSLLN8nvGRY1mByWLwPuWF3zYVX3Luji9nMmYRqNKCWZbhe+SFTxwJ\r\n
   yWLYYgYXqZDkw0pdgI2g62Xb9xfsySAIIbcphrceDUM1E82w5A==\r\n
  List-Unsubscribe: <http://localhost/unsubscribe?token=eyJpdiI6IjRhRGNvdnJHWEdFd1pRWmZhNmhBbFE9PSIsInZhbHVlIjoiSit0WmxBWE5JSTdvK3FCNjFJempDUWY3SmVEWFo0NXZhTjRDc215cmxUUT0iLCJtYWMiOiJhNmJkNmQyMmVlMzI3YjJmZDdkMGMyMGRiNjhhYjhiNTgyOTEyZGFjYTFhNzkzZWIxOTQ5MzU1MTlhZDJkMjYzIiwidGFnIjoiIn0%3D>\r\n
  List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n
  Content-Type: text/html; charset=utf-8\r\n
  Content-Transfer-Encoding: quoted-printable\r\n
  \r\n
  <html>\r\n
      <body>\r\n
          <h1>=E3=83=A1=E3=83=AB=E3=83=9E=E3=82=AC=E3=81=AE=E9=85=8D=E4=BF=\r\n
  =A1=E3=81=A7=E3=81=99</h1>\r\n
          <p>=E3=81=84=E3=81=A4=E3=82=82=E3=83=A1=E3=83=AB=E3=83=9E=E3=82=\r\n
  =AC=E3=82=92=E8=B3=BC=E8=AA=AD=E3=81=97=E3=81=A6=E9=A0=82=E3=81=84=E3=81=\r\n
  =A6=E3=81=82=E3=82=8A=E3=81=8C=E3=81=A8=E3=81=86=E3=81=94=E3=81=96=E3=81=\r\n
  =84=E3=81=BE=E3=81=99=E3=80=82</p>\r\n
      </body>\r\n
  </html>\r\n
  """

>

この文字列をファイルに保存して、dkimverifyのコマンドにフィードすればユニットテストの作成が可能ですね。

ユニットテストを作成

以下、Dkim署名のユニットテストを作成してみました。

namespace Tests\Unit;

use Tests\TestCase;
use App\Mail\Newsletter;
use Illuminate\Support\Facades\Mail;

class NewsletterTest extends TestCase
{
    protected $exportFileName, $emlPath;

    protected function setUp(): void
    {
        parent::setUp();

        // テスト用のファイル名を設定
        $this->exportFileName = 'NewsletterTest.eml';
        $this->emlPath = storage_path('app/'.$this->exportFileName);

        // テスト用のファイルが存在する場合は削除
        @unlink($this->emlPath);
    }

    /**
     * @test
     *
     * @covers App\Mail\NewsletterMessage
     */
    public function buildWithDkim(): void
    {
        $email = 'test@example.com';

        $mailable = new Newsletter($email);

        // メール送信して文字列化
        $data = Mail::to($email)->send($mailable)->toString();

        // メールをファイルに保存
        file_put_contents($this->emlPath, $data);

        //DKIM情報チェック
        $output = shell_exec('/usr/local/bin/dkimverify < '.$this->emlPath);
        $this->assertEquals("signature ok\n", $output);
    }
}

テストを実行してみましょう。

$ php artisan test --filter NewsletterTest

   PASS  Tests\Unit\NewsletterTest
  ✓ build with dkim                                                                                                                                                                                       0.33s

  Tests:    1 passed (1 assertions)
  Duration: 0.37s

問題なくパスです。

By khino