ことの発端は仕事でWebアプリを作ってて、メール送信機能を実装したときのこと。送信ボタンを押してからメールが受信されるまで4〜6秒もかかってて、これはさすがにヤバいと思ったのがきっかけ。
メール送信が遅い理由を調べてみた結果
最初はなんでこんなに遅いのか理解できなかった。普通にMail::send()を使ってるだけなのに。
調べてみると、Laravelでメールを送信するときは以下の処理が同期的に実行されるらしい:
- メール内容の組み立て
- SMTPサーバーへの接続
- メールの送信処理
- 接続の終了
要するに、この全部が終わるまでユーザーは待たされるってこと。特にSMTPサーバーとの通信が外部サービスとのやり取りになるから、ネットワークの状況によってはもっと時間がかかることもある。
これは確実に改善しないとダメだと思った。
キューって何?
キューは簡単に言うと、時間のかかる処理を後回しにしてバックグラウンドで実行する仕組み。
メール送信で例えると:
- ユーザーが送信ボタンを押す
- 「メール送信タスクをキューに追加」だけして即座にレスポンス
- 実際のメール送信は別のプロセスで非同期に処理
これなら待機時間ゼロで画面が切り替わるし、メール送信も確実に実行される。理想的すぎる。
事前準備も忘れずに
キューを使う前に、.env ファイルでメール設定をしておく必要がある。Mailtrapを使う場合は以下のような感じ:
MAIL_MAILER=smtp MAIL_HOST=mailpit MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_NAME="${APP_NAME}"
あと、キューのマイグレーションも実行しておく:
php artisan queue:table php artisan migrate
これで jobs テーブル、job_batches テーブル、failed_jobs テーブルが作成される。
実際にやってみた
最初の実装(遅い版)
まず、普通にメール送信を実装してみた。
// routes/web.php Route::get('send-mail', function () { Mail::send(new PostPublished()); dd('mail sent'); });
メールクラスはartisanコマンドで作成:
php artisan make:mail PostPublished
これで app/Mail/PostPublished.php が作成される。中身を以下のように編集:
public function envelope() { return new Envelope( to: 'test@gmail.com', subject: 'Post Published', ); } public function content() { return new Content( view: 'welcome', ); }
これでブラウザから /send-mail にアクセスしてみると、案の定4〜5秒くらい待たされる。やっぱり遅い。
キューを使った実装(高速版)
次にキューを使って改良してみた。まずジョブクラスを作成:
php artisan make:job SendMail
これで app/Jobs/SendMail.php が作成される。作ったジョブクラスに元のメール送信ロジックを移動:
<?php namespace App\Jobs; use App\Mail\PostPublished; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; class SendMail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function handle() { Mail::send(new PostPublished()); } }
routes/web.php ファイルを以下のように修正:
// routes/web.php Route::get('/send-mail', function () { SendMail::dispatch(); dd('mail sent'); });
結果が驚きすぎた
改良後にもう一度アクセスしてみると、一瞬で「mail sent」が表示された。マジで秒速。
メールが送信されてない場合は、キューにジョブを追加しただけで、実際にメールを送信するワーカーが動いてなかったから。
ワーカーを起動するコマンドを実行する必要がある:
php artisan queue:work
これを実行してからもう一度試すと、画面は一瞬で切り替わるし、ちゃんとメールも送信される。完璧。
ビフォーアフターの比較
改良前:
- ユーザーがアクセス
- 4〜5秒の待機時間(地獄)
- メール送信完了
改良後:
- ユーザーがアクセス
- バックグラウンドでメール送信処理
この差は歴然。ユーザーエクスペリエンスが段違いに良くなった。
他にも使える場面がたくさんある
キューはメール送信以外でも使える:
- 大容量ファイルのアップロード処理
- 画像のリサイズやフォーマット変換
- データの集計処理やレポート生成
- 外部APIとの通信処理
要するに時間のかかる処理は全部キューに投げられる。これはかなり使える機能だと思った。
まとめ
Laravelのキュー機能を使えば、メール送信の待機時間問題は簡単に解決できる。実装も思ってたより簡単だし、効果は抜群。
ユーザビリティの改善って、こういう小さい積み重ねが大事だと改めて思った。待機時間が長いだけでユーザーは離脱しちゃうからね。
もしメール送信で同じ悩みを抱えてる人がいたら、ぜひキューを試してみてほしい。マジで世界が変わる。