Laravel『Mail Not Sent』の原因と対処法

Laravel『Mail Not Sent』の原因と対処法

Laravelで「Mail Not Sent(メールが送れない)」状態は、Mailファサードの呼び出し自体が失敗して例外が出るケースだけでなく、キューに積まれて実行されていない、SMTP接続はできたが送信先に拒否された、送信は成功したが迷惑メール側に落ちた、など複数の層で起きる。まず「同期送信かキュー送信か」「Laravelが例外を出しているか(ログ)」「SMTP/送信サービスの応答(接続・認証・レート制限)」「受信側の拒否(SPF/DKIM/DMARC、From不正)」を切り分けると復旧が速い。

症状と発生条件(典型例)

発生条件の典型:
・Mail::to(…)->send(…) を呼ぶと例外が出る(接続不可、認証失敗、TLS失敗など)
・処理は成功するがメールが届かない(キュー未稼働、迷惑メール、バウンス)
・キュー送信(ShouldQueue)にしたら送れなくなった(worker未起動、失敗ジョブ溜まり)
・本番だけ送れない(ファイアウォールで25/587閉塞、DNS/SPF問題、送信元ドメイン制限)
よくあるログ/例外キーワード:

Connection could not be established with host
Failed to authenticate on SMTP server
Connection timed out
SSL: certificate verify failed
Expected response code 250 but got code "550"
Too many login attempts / Rate limit exceeded

最初の切り分け:同期送信か、キュー送信か

Laravelは Mailable が ShouldQueue を実装していたり、MAIL_MAILER の設定やコード側で queue() を呼ぶとキュー送信になる。
・同期:send()
・キュー:queue() / later() / ShouldQueue
キュー送信のまま worker が動いていないと「送信したつもり」になる。

// 同期
Mail::to($user->email)->send(new WelcomeMail($user));

// キュー
Mail::to($user->email)->queue(new WelcomeMail($user));

まずは一時的に send() にして送信できるかを確認すると、キュー要因を早期に排除できる。

原因1:.env のメール設定不備(MAIL_MAILER / HOST / PORT / USERNAME / PASSWORD)

最頻出。特に本番で .env が違う、設定キャッシュが古い、が混ざるとハマりやすい。
確認する代表項目(例:SMTPの場合)。

MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your_user
MAIL_PASSWORD=your_pass
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="Example"

設定を変えたら config キャッシュが残っていないかも確認する。

php artisan config:clear
php artisan cache:clear

原因2:SMTPへの接続不可(ネットワーク/ファイアウォール/ポート閉塞)

発生条件の典型:
・クラウド/レンタルサーバで outbound 25 が塞がれている
・587/465 もセキュリティグループで閉じている
・DNS解決できない
・社内ネットワークから外部SMTPへ出られない
対処:
・587(STARTTLS)を優先して試す
・送信サービス(SES/SendGrid/Mailgun等)を利用する場合は推奨ポートに合わせる
・疎通確認(サーバ側で実施)

# DNS
nslookup smtp.example.com

# TCP疎通(環境により nc/telnet が使える)
nc -vz smtp.example.com 587

接続タイムアウトが出るなら、アプリ以前にネットワーク層の問題が濃厚。

原因3:認証失敗(ユーザー/パスワード、アプリパスワード、二要素認証)

発生条件の典型:
・SMTPの資格情報が違う
・Gmail等で「アプリパスワード」が必要(2FA有効)
・送信サービスがAPIキー/SMTPキーを要求しているのに通常パスを入れている
対処:
・メールサービス側の「SMTP認証情報」を再発行
・.env の余計な空白や引用符の崩れを排除
・同一資格情報で他のクライアントから送れるか確認(切り分け)

原因4:暗号化/TLSの不整合(tls/ssl、証明書検証)

発生条件の典型:
・587なのに ssl を指定している / 465なのに tls を指定している
・証明書検証エラー(ca-certificates不足、古いOS)
対処:
・587 → MAIL_ENCRYPTION=tls
・465 → MAIL_ENCRYPTION=ssl

# 587
MAIL_PORT=587
MAIL_ENCRYPTION=tls

# 465
MAIL_PORT=465
MAIL_ENCRYPTION=ssl

証明書検証で落ちる場合、OSのCA更新が必要なことがある(アプリ側の回避より、基盤更新が安全)。

原因5:受信側に拒否されている(550/554、Fromドメイン制限、SPF/DKIM/DMARC)

SMTP的には送信処理中に「550」等で弾かれ、Laravel側では例外や送信失敗として現れることがある。
発生条件の典型:
・MAIL_FROM_ADDRESS のドメインが送信サービスで認証されていない
・SPF/DKIM未設定でDMARCが厳しく、受信側が拒否
・FromとReturn-Pathの整合が取れていない
対処:
・送信サービス側でドメイン認証(SPF/DKIM)
・Fromは“認証済みドメイン”に統一
・エラーメッセージの応答コード(550など)をログから抜き出して、その文言の通りに修正する

原因6:キュー送信のworkerが動いていない/失敗ジョブが溜まっている

キュー化した瞬間に「送れない」になるパターン。
発生条件:
・queue:work を動かしていない
・Supervisor/systemd設定がなく、プロセスが落ちている
・失敗ジョブ(failed_jobs)が増えている
対処:
・workerの稼働確認

php artisan queue:work

・失敗ジョブ確認

php artisan queue:failed

・ジョブ再実行

php artisan queue:retry all

本番運用では常駐プロセス管理(Supervisor等)で worker を維持する前提になる。

原因7:ローカル/開発環境で “送れたように見えるが届かない” 設定

開発環境で mailpit/mailhog/log ドライバを使っていると外部には届かない。
発生条件の典型:
・MAIL_MAILER=log(ログに出るだけ)
・MAIL_MAILER=array(メモリ内で破棄)
対処:
・本当に外部送信する環境では smtp などにする

MAIL_MAILER=smtp

開発は mailpit、ステージング/本番は smtp のように環境で切り替える。

サンプル:送信テスト用ルート(例外を握り潰さずログに残す)

切り分け用に最小の送信を作ると原因が浮き出やすい。

use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;

Route::get('/mail-test', function () {
    try {
        Mail::raw('mail test', function ($message) {
            $message->to('you@example.com')
                    ->subject('Mail Test');
        });

        return response('OK', 200);
    } catch (\Throwable $e) {
        Log::error('mail failed', ['error' => $e->getMessage()]);
        return response('NG: '.$e->getMessage(), 500);
    }
});

キュー要因を避けるため、最初は raw + 同期送信で確認する。

チェックリスト(上から順に確認する)

1) 同期(send)かキュー(queue/ShouldQueue)かを切り分けたか
2) storage/logs/laravel.log にSMTP応答や例外メッセージ(550/timeout/auth)が残っているか
3) .env の MAIL_MAILER/HOST/PORT/USERNAME/PASSWORD/ENCRYPTION/FROM が正しいか
4) 設定変更後に config:clear を実施し、古い設定キャッシュを排除したか
5) サーバからSMTP先へ疎通できるか(DNS/ポート587/465、ファイアウォール)
6) 認証方式(アプリパスワード/SMTPキー)に合った資格情報を使っているか
7) 受信側拒否(550/DMARC等)の場合、Fromドメイン認証(SPF/DKIM)が整っているか
8) キュー送信なら worker が常時稼働し、failed_jobs が溜まっていないか