📤

Laravel9のMailableで送信前&送信後のMessage-Idを取得する

2023/01/14に公開

Laravel9

% php artisan --version
Laravel Framework 9.44.0

まず答えから。

app/Events/OnMessageSent.php

<?php
namespace App\Events;

use Illuminate\Mail\Events\MessageSent;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Mime\Message;

class OnMessageSent {
    public function handle(MessageSent $event)
    {
           // とりあえずコレとっとけばOK
        $id = $event->sent->getMessageId();
        
        // Laravelが生成するMessage-IDも取得したい場合
        $local_message_id = null;
        $orig = $event->sent->getOriginalMessage();
        if ($orig instanceof Message) {
            $local_message_id = $orig->getPreparedHeaders()->get('Message-ID')->getBodyAsString();
        }
        
        //Log::info($event->sent->getDebug());
        Log::info('real_smtp_id='.$id."\t".'local_smtp_id='. $local_message_id);
    }
}

app/Providers/EventServiceProvider.php
<?php

namespace App\Providers;

use App\Events\OnMessageSent;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Mail\Events\MessageSent;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event to listener mappings for the application.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
	/* ↓追加 */
        MessageSent::class=>[
            OnMessageSent::class
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Determine if events and listeners should be automatically discovered.
     *
     * @return bool
     */
    public function shouldDiscoverEvents()
    {
        return false;
    }
}

app/Mail/TestEmail.php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class TestEmail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Get the message envelope.
     *
     * @return \Illuminate\Mail\Mailables\Envelope
     */
    public function envelope()
    {
        return new Envelope(
            subject: 'Test Email',
        );
    }

    /**
     * Get the message content definition.
     *
     * @return \Illuminate\Mail\Mailables\Content
     */
    public function content()
    {
        return new Content(
            view: 'email.dummy',
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array
     */
    public function attachments()
    {
        return [];
    }
}

views/email/dummy.blade.php
Hello.

ログ

local.INFO: real_smtp_id=MlTFAbRjSeaWDcGIscbqkg	local_smtp_id=<0225de0cb4b78e5b2fea7567a6321904@example.com>  

以上。

ログも取得したいなら$event->sent->getDebug()で取得可能

< 220 SG ESMTP service ready at geopod-ismtpd-4-0
> EHLO [127.0.0.1]
< 250-smtp.sendgrid.net
< 250-8BITMIME
< 250-PIPELINING
< 250-SIZE 31457280
< 250-STARTTLS
< 250-AUTH PLAIN LOGIN
< 250 AUTH=PLAIN LOGIN
> STARTTLS
< 220 Begin TLS negotiation now
> EHLO [127.0.0.1]
< 250-smtp.sendgrid.net
< 250-8BITMIME
< 250-PIPELINING
< 250-SIZE 31457280
< 250-STARTTLS
< 250-AUTH PLAIN LOGIN
< 250 AUTH=PLAIN LOGIN
> AUTH LOGIN
< 334 ...
> YXBpa2V5
< 334 ...
> ..
< 235 Authentication successful
> MAIL FROM:<hello@example.com>
< 250 Sender address accepted
> RCPT TO:<test@gmail.com>
< 250 Recipient address accepted
> DATA
< 354 Continue
> .
< 250 Ok: queued as bgPXyQjSTT2yy-5Rvj8PRA
php artisan tinker
use App\Mail\TestEmail;
$a = Mail::to('xxxx@example.com')->send(new TestEmail());
dd($a);
  -raw: Symfony\Component\Mime\RawMessage {#3707
      -message: """
        From: Laravel <hello@example.com>\r\n
        To: test@gmail.com\r\n
        Subject: Test Email\r\n
        Message-ID: <7bb34b09a49584f934895e1e305b5a41@example.com>\r\n
        MIME-Version: 1.0\r\n
        Date: Fri, 13 Jan 2023 15:14:06 +0000\r\n
        Content-Type: text/html; charset=utf-8\r\n
        Content-Transfer-Encoding: quoted-printable\r\n
        \r\n
        Hello.\r\n
        """
	
	...
 -messageId: "0iqWppCFTYOmGIKsVSK3Yg"
    -debug: """
      > MAIL FROM:<hello@example.com>\n
      < 250 Sender address accepted\r\n
      > RCPT TO:<test@gmail.com>\n
      < 250 Recipient address accepted\r\n
      > DATA\n
      < 354 Continue\r\n
      > .\n
      < 250 Ok: queued as 0iqWppCFTYOmGIKsVSK3Yg\r\n
      """
  }

ここでSendGridを利用した場合のMessage-Idを確認してみます

<SendGridが生成する識別情報を利用する方法>
Web API v3またはSMTPをご利用の場合で、独自の識別情報を付与する必要がなければ、自動で生成される「X-Message-ID」および「SendGrid Message ID」をご利用ください。
SendGridは送信リクエストを受け付けると、リクエストごとにX-Message-IDを生成します。この情報は、送信リクエストのレスポンスとして返却されます。その後メール送信処理が始まると、メールごとにSendGrid Message IDが付与されます。SendGrid Message IDは発生したイベントの各データに含まれます。
SendGrid Message IDは、X-Message-IDの後ろに各メールの識別用の文字列を付加した形になっています。そのため、X-Message-IDとSendGrid Message IDを送信側で保持しておけば、送信リクエストと発生した各イベントデータを紐付けることができます。
https://support.sendgrid.kke.co.jp/hc/ja/articles/360000222542

SendGrid経由で届いたメールのヘッダのMessage-IDは、SendGrid側で再生成されたものが利用されるようです。

From: Laravel <hello@example.com>
Subject: Test Email
Message-ID: <7bb34b09a49584f934895e1e305b5a41@example.com>
MIME-Version: 1.0
Date: Fri, 13 Jan 2023 15:14:07 +0000 (UTC)
X-Feedback-ID: 2864175:SG

自社のPostfixとかSendGridとかメール配信プロバイダを混合させる場合は2つのMessage-IDを保持したほうがよさそうです。

Laravel8

SwiftMailerのヘンで正しいMessageIdを返却しないので
ヘッダのバケツリレーまたはSwift_Events_SendListenerを補足するか、など
大変なので頑張ってください。

https://github.com/laravel/framework/issues/28120#issuecomment-531598809

なお、SwiftMailerのサポートは終了しています。

Discussion