😇
Laravel 12の認証メールが“生HTML表示”になる原因と解決 — Markdown通知からカスタムHTMLへ
この記事で解決できること
- 認証メールの本文に
<table class="action" ...>
や<a href="...">
などの“生HTML”がそのまま表示される - メールクライアントやプレビューによって、ボタンがテキスト化・崩れる
- Markdownベースの通知テンプレートを使った際の表示ゆれをなくしたい
[!NOTE]
本記事は Laravel 12 + Inertia.js のプロジェクトで遭遇した事象をもとにしています。結論として「通知を完全なHTMLビューで送る」方式に切り替えると安定します。
結論(TL;DR)
- 原因: Laravelの「Markdown通知メール」テンプレート(
<x-mail::message>
)に生HTML(独自の<a>
やテーブル)を混在させると、レンダラや一部メーラーがエスケープし、本文にHTML文字列が露出することがある - 解決: Markdown通知をやめて、通知クラスから完全なHTMLビューを指定して送信する
- 効果: ほぼすべてのメーラーで「ボタン付きの日本語メール」が安定表示される
症状の例
- 認証メール本文に以下のような文字列がそのまま出る
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
...
</table>
<a href="https://..." style="...">メールアドレスを認証する</a>
- クライアントやプレビュー(ログ出力ビューア等)により、ボタンが出たり出なかったりする
[!WARNING]
Markdown通知は“Markdownとして安全に書く”のが前提です。生HTMLを混ぜると、テキスト版生成時やエスケープ処理で崩れるリスクがあります。
解決策の全体像
-
User
モデルで、デフォルトのメール確認通知をカスタム通知に差し替える - カスタム通知クラスで
toMail()
をHTMLビュー送信に変更 - 独自のHTMLメールテンプレート(インラインCSS)を用意
実装手順
User
モデルで通知を差し替え
1) // app/Models/User.php(抜粋)
use App\Notifications\CustomVerifyEmail;
public function sendEmailVerificationNotification()
{
$this->notify(new CustomVerifyEmail);
}
2) カスタム通知クラス(HTMLビューを使う)
// app/Notifications/CustomVerifyEmail.php(抜粋)
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
class CustomVerifyEmail extends VerifyEmail
{
public function toMail($notifiable): MailMessage
{
$verificationUrl = $this->verificationUrl($notifiable);
return (new MailMessage)
->subject('メールアドレスの認証をお願いします')
->view('mail.verify-email', [
'actionUrl' => $verificationUrl,
'appName' => config('app.name'),
]);
}
}
3) HTMLメールテンプレートを作成
- ファイル:
resources/views/mail/verify-email.blade.php
- ポイント: メール互換性のためテーブルレイアウト+インラインCSS
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ $appName }} - メール認証</title>
</head>
<body style="margin:0;padding:0;background:#f3f7fb;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#0f172a;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#e9eff5;padding:24px 0;">
<tr>
<td align="center">
<table
role="presentation"
width="600"
cellpadding="0"
cellspacing="0"
style="background:#ffffff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(15,23,42,0.08);"
>
<tr>
<td style="padding:24px 28px;">
<h1 style="margin:0 0 8px 0;font-size:20px;">こんにちは!</h1>
<p style="margin:0 0 16px 0;line-height:1.7;">
{{ $appName }} にご登録いただき、ありがとうございます。<br />
以下のボタンをクリックしてメールアドレスの認証を完了してください。
</p>
<table role="presentation" align="center" cellpadding="0" cellspacing="0" style="margin:24px auto;">
<tr>
<td align="center" bgcolor="#0ea5e9" style="border-radius:8px;">
<a
href="{{ $actionUrl }}"
style="display:inline-block;padding:12px 24px;color:#ffffff;text-decoration:none;font-weight:700;"
>
メールアドレスを認証する
</a>
</td>
</tr>
</table>
<p style="margin:0 0 8px 0;line-height:1.7;">このメールに心当たりがない場合は、何もする必要はありません。</p>
<p style="margin:16px 0 0 0;line-height:1.7;">よろしくお願いいたします。<br />{{ $appName }}</p>
</td>
</tr>
<tr>
<td style="padding:16px 28px;background:#f8fafc;border-top:1px solid #e2e8f0;">
<p style="margin:0; font-size:13px; color:#475569;">
ボタンがクリックできない場合は以下のURLをブラウザに貼り付けてください:<br />
<a href="{{ $actionUrl }}" style="color:#0ea5e9;word-break:break-all;">{{ $actionUrl }}</a>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
よくある落とし穴と対策
-
落とし穴: Markdown通知テンプレートに生HTMLを混ぜる
- 対策: すべてMarkdownコンポーネントで統一するか、今回のようにHTMLビュー送信へ切替
-
落とし穴: テキスト版での可読性が崩れる
- 対策: HTMLビューに加えて、必要に応じてテキスト版テンプレートも用意
-
落とし穴: 本番でURLがおかしい
-
対策:
.env
のAPP_URL
を本番ドメインに、MAIL_FROM_NAME
を日本語名に設定
-
対策:
[!TIP]
運用では SPF/DKIM/DMARC のセットアップも忘れずに。配信到達率や迷惑メール判定に影響します。
テスト観点(チェックリスト)
- 新規登録時に自動で認証メールが届く
- HTML版でボタンが表示され、テキスト版にも最低限の導線がある
- 主要メーラー(Gmail, iCloud, Outlook など)で崩れない
- 期限切れURLや多回送も想定して再送機能を確認
まとめ
- Markdown通知は便利だが、生HTML混在で“生HTML表示”になることがある
- 最も安定するのは「通知→HTMLビュー送信」
- 本記事の実装で、日本語のボタン付きメールがほぼすべてのメーラーで安定表示されるようになる
[!NOTE]
プロジェクトでは Google 認証(Socialite)とも併用し、登録導線はメール・Googleともに同じ認証ページで統一しています。運用体験も揃えられておすすめです。
Discussion