なぜメール送信を非同期化するのか
Webアプリでメール送信をリクエスト処理の中で同期的に実行すると、以下の問題が発生します。
- SMTPサーバーへの接続時間(数百ms〜数秒)がレスポンスタイムに加算される
- 送信に失敗したとき、ユーザーへのレスポンスも失敗してしまう
- 大量送信時にタイムアウトが発生しやすい
Laravelのキューシステムを使うことで、メール送信を「後でまとめて処理する」非同期処理に変換でき、ユーザーには即座にレスポンスを返せます。
キューの設定
.envでキュードライバーを設定
# 開発環境:同期実行(キューなしで即実行)
QUEUE_CONNECTION=sync
# 本番環境:データベースキュー
QUEUE_CONNECTION=database
# または Redis キュー(推奨)
QUEUE_CONNECTION=redis
データベースキューのマイグレーション
php artisan queue:table
php artisan migrate
Mailableクラスの作成
php artisan make:mail ContactReceived
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class ContactReceived extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public readonly array $contactData,
) {}
public function envelope(): Envelope
{
return new Envelope(
subject: "【お問い合わせ受付】{$this->contactData['subject']}",
);
}
public function content(): Content
{
return new Content(
view: 'emails.contact-received',
with: [
'name' => $this->contactData['name'],
'email' => $this->contactData['email'],
'subject' => $this->contactData['subject'],
'body' => $this->contactData['body'],
],
);
}
public function attachments(): array
{
return [];
}
}
キューにジョブを投入する
Mail::send() の代わりに Mail::queue() を使うだけです。
// コントローラー
class ContactController extends Controller
{
public function store(ContactRequest $request): RedirectResponse
{
$data = $request->validated();
// DBに保存
$contact = Contact::create($data);
// キューに投入(即座に返る)
Mail::to(config('mail.admin'))
->queue(new ContactReceived($data));
// 送信者への自動返信もキューで
Mail::to($data['email'])
->queue(new ContactAutoReply($data));
return redirect()
->route('contact.complete')
->with('success', 'お問い合わせを受け付けました。確認メールをお送りします。');
}
}
送信遅延
特定の時間に遅らせて送ることもできます。
// 5分後に送信
Mail::to($user->email)
->later(now()->addMinutes(5), new WelcomeMail($user));
// 翌朝9時に送信
Mail::to($user->email)
->later(now()->setHour(9)->addDay()->startOfHour(), new DailySummary($summary));
キューワーカーの起動
# ワーカーを起動(前景実行)
php artisan queue:work
# 失敗ジョブを再試行
php artisan queue:retry all
# キューの状態確認
php artisan queue:monitor
Supervisorで常時起動
本番環境ではSupervisorを使ってワーカーを常時起動します。
; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
失敗ジョブのハンドリング
# 失敗ジョブテーブルを作成
php artisan queue:failed-table
php artisan migrate
// Mailableにfailedメソッドを追加
class ContactReceived extends Mailable
{
public function failed(\Throwable $e): void
{
// Slackに通知するなど
Log::error("メール送信失敗: {$this->contactData['email']}", [
'error' => $e->getMessage(),
]);
}
}
メールテスト
開発環境でのメール確認には、Mailpit が便利です。
# .env
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
本番環境ではAmazon SES・SendGrid・Mailgunなどのサービスを利用することを推奨します。
まとめ
Laravelのキューを使ったメール送信の非同期化は、ユーザー体験の向上とシステムの堅牢性強化に直結します。Mail::queue() への変更はたった1単語ですが、その効果は大きいです。弊社では問い合わせ・予約確認・通知メールすべてにキューを活用しています。
LaravelのWebシステム開発についてはお気軽にご相談ください。