【PHP】LaravelでMailableを使ってメールを送信する方法

対象バージョン: Laravel 13.x / PHP 8.3+

Laravel では、送信するメールを Mailable というクラスで表します。この記事では、Mailable の作成から送信、実際には送らずに中身を確認する方法、テストでの検証までを順に説明します。SMTP サーバーや外部サービスのアカウントは使いません。新規プロジェクトの初期設定のまま試せます。

この記事で分かること

  • php artisan make:mail で Mailable を作り、件名・本文・データを定義する方法
  • Mail::to()->send() で送信し、cc / bcc を付ける方法
  • log メーラー、ブラウザプレビュー、render() で送信内容を確認する方法
  • Mail::fake() と assertSent 系メソッドで送信をテストする方法

前提

Laravel 13.x は PHP 8.3 以上が必要です。動作確認は laravel/framework v13.35.0 で行いました。ルーティングや Blade の基本は、【PHP】Laravel入門:インストールから簡単なサンプルアプリケーションの作成を参照してください。

メールの設定を確認する

メールの設定は config/mail.php にあります。mailers 配列にメーラー(送信方法)ごとの設定が並び、default で既定のメーラーを選びます。

'default' => env('MAIL_MAILER', 'log'),

Laravel 13.x の新規プロジェクトでは、.env が次の値になっています。

MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

MAIL_MAILER=log なので、初期状態ではメールは実際には送られず、ログファイルに書き出されます。送信元(From)は config/mail.php の from で全体に設定され、MAIL_FROM_ADDRESS と MAIL_FROM_NAME から読み込まれます。

Mailable クラスを作る

Mailable は make:mail コマンドで作成します。ここでは発送通知メールを例にします。

php artisan make:mail ShippingNotice

クラスは app/Mail ディレクトリに置かれます。このディレクトリは最初の Mailable を作るときに自動で作成されます。

生成されたクラスは Illuminate\Mail\Mailable を継承し、次の3つのメソッドを持っています。

メソッド 戻り値 役割
envelope() Envelope 件名などを定義する
content() Content 本文に使う Blade ビューを指定する
attachments() 配列 添付ファイル(初期状態は空)

生成直後の content() には view: 'view.name' という仮の値が入っています。ビューファイルは作られないため、自分で用意します。

件名・本文・データを定義する

注文番号と顧客名を受け取るように書き換えます。

<?php

namespace App\Mail;

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

class ShippingNotice extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(
        public string $orderNumber,
        protected string $customerName,
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'ご注文の商品を発送しました',
        );
    }

    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
            with: [
                'customerName' => $this->customerName,
            ],
        );
    }

    /**
     * @return array<int, Attachment>
     */
    public function attachments(): array
    {
        return [];
    }
}

ビューにデータを渡す方法は2つあります。

  • public プロパティ: $orderNumber のような public プロパティは、そのままビューで使えます。
  • with: パラメータ: Content の with: で明示的に渡します。この場合、値は protected または private プロパティに持たせて、ビューへ自動で公開されないようにします。

ビューは resources/views/mail/orders/shipped.blade.php に作ります。

<!DOCTYPE html>
<html lang="ja">
<body>
    <p>{{ $customerName }} 様</p>
    <p>ご注文(注文番号: {{ $orderNumber }})の商品を発送しました。</p>
</body>
</html>

メールを送信する

送信には Mail ファサードを使います。to() に宛先を指定し、send() に Mailable のインスタンスを渡します。cc() と bcc() はメソッドチェーンで追加できます。

use App\Mail\ShippingNotice;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route;

Route::get('/orders/ship', function () {
    Mail::to('taro@example.com')
        ->cc('cc@example.com')
        ->bcc('bcc@example.com')
        ->send(new ShippingNotice('A-1001', '山田太郎'));

    return 'sent';
});

公式ドキュメントによると、to() にはメールアドレスのほか、ユーザーのインスタンスやそのコレクションも渡せます。

make:mail で作ったクラスは ShouldQueue を実装していないため、send() はその場で同期的に送信します。キューを使う送信(Mail::to()->queue())もありますが、事前にキューの設定が必要です。

log メーラーで送信内容を確認する

MAIL_MAILER=log のとき、メールは送信されずにアプリケーションのログへ書き出されます。ローカル開発向けの仕組みです。新規プロジェクトでは storage/logs/laravel.log に、local.DEBUG のエントリとしてメール全体が記録されます。

上の /orders/ship にアクセスすると、次のように出力されます。

[2026-10-09 06:40:26] local.DEBUG: From: Laravel <hello@example.com>
To: taro@example.com
Cc: cc@example.com
Subject: =?utf-8?Q?=E3=81=94=E6=B3=A8=E6=96=87=E3=81=AE?=
 =?utf-8?Q?=E5=95=86=E5=93=81=E3=82=92=E7=99=BA?=
 =?utf-8?Q?=E9=80=81=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F?=
MIME-Version: 1.0
Date: Fri, 09 Oct 2026 06:40:26 +0000
Message-ID: <821cf9c22755d50fcac46c47c3f8d2d6@example.com>
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE html>
<html lang="ja">
<body>
    <p>山田太郎 様</p>
    <p>ご注文(注文番号: A-1001)の商品を発送しました。</p>
</body>
</html>

ログを読むときは、次の3点に注意してください。

  • 時刻は UTC: 新規プロジェクトでは config/app.php の timezone が 'UTC' のため、ログの時刻は日本時間より9時間前になります。
  • 日本語の件名はエンコードされる: 件名は =?utf-8?Q?...?= の形式で記録されるため、そのままでは読めません。本文の HTML は読める形で出力されます。
  • Bcc はログに出ない: To と Cc は表示されますが、Bcc の行は出力されません。Bcc の宛先は、後述するテストで確認します。

公式ドキュメントでは、log メーラーの代わりに HELO や Mailtrap などのサービスを smtp ドライバで使う方法や、Laravel Sail の Mailpit を使う方法も紹介されています。

送信せずにプレビューする

ブラウザで表示する

ルートやコントローラから Mailable を返すと、メールはブラウザに HTML として表示されます。このとき送信は行われません。

use App\Mail\ShippingNotice;
use Illuminate\Support\Facades\Route;

Route::get('/mailable/shipping-notice', function () {
    return new ShippingNotice('A-1001', '山田太郎');
});

render() で HTML を取得する

render() を呼ぶと、評価済みの HTML を文字列で取得できます。こちらも送信はしません。

php artisan tinker
(new App\Mail\ShippingNotice('A-1002', '佐藤花子'))->render();

Markdown メールを使う

--markdown オプションを付けると、Markdown 形式のビュー付きで Mailable を作れます。

php artisan make:mail WelcomeMail --markdown=mail.welcome

生成されるクラスの content() は Content(markdown: 'mail.welcome') になり、resources/views/mail/welcome.blade.php も作成されます。

<x-mail::message>
# Introduction

The body of your message.

<x-mail::button :url="''">
Button Text
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

この Mailable をルートから返してブラウザで表示すると、ボタンを含むメール用のレイアウトで描画されます。

メール送信をテストする

テストの基本は【PHP】Laravelでテストを実装する方法で扱っています。ここではメール固有の部分を説明します。

Mail::fake() で送信を検証する

Mail::fake() を呼ぶと、メールは実際には送られません。そのうえで、送信されたかどうかを検証できます。

<?php

namespace Tests\Feature;

use App\Mail\ShippingNotice;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;

class ShippingNoticeTest extends TestCase
{
    public function test_shipping_notice_is_sent(): void
    {
        Mail::fake();

        $this->get('/orders/ship')->assertOk();

        Mail::assertSent(ShippingNotice::class);
        Mail::assertSent(ShippingNotice::class, function (ShippingNotice $mail) {
            return $mail->hasTo('taro@example.com')
                && $mail->hasCc('cc@example.com')
                && $mail->hasBcc('bcc@example.com')
                && $mail->orderNumber === 'A-1001';
        });
        Mail::assertSentCount(1);
    }

    public function test_nothing_is_sent_when_route_not_called(): void
    {
        Mail::fake();

        Mail::assertNothingSent();
    }
}
メソッド 検証内容
Mail::assertSent(クラス名) 指定した Mailable が送信された
Mail::assertSent(クラス名, クロージャ) 条件に合う Mailable が送信された
Mail::assertSentCount(n) 送信された Mailable が n 件
Mail::assertNothingSent() 何も送信されていない

クロージャ内の hasBcc() で、ログでは確認できない Bcc の宛先も検証できます。

1回だけ送信されたことを確認する

「ちょうど1回送信された」ことは、Mail::assertSentOnce() で確認できます。

Mail::assertSentOnce(ShippingNotice::class);

注意: Mail::assertSentOnce() は Laravel 13.31 以降で使えます。 Laravel 13.0〜13.30.x にはこのメソッドがありません。使っているバージョンは php artisan --version で確認できます。13.31 より前のバージョンでは、回数を指定した assertSent を使います。

Mail::assertSent(ShippingNotice::class, 1);

メールの内容を検証する

件名や本文は、Mail::fake() を使わずに Mailable のインスタンスで直接検証できます。

public function test_shipping_notice_content(): void
{
    $mailable = new ShippingNotice('A-1001', '山田太郎');

    $mailable->assertHasSubject('ご注文の商品を発送しました');
    $mailable->assertSeeInHtml('山田太郎 様');
    $mailable->assertSeeInHtml('A-1001');
    $mailable->assertDontSeeInHtml('password');
}

テストは次のコマンドで実行します。

php artisan test

まとめ

  • Mailable は php artisan make:mail で作り、envelope() に件名、content() にビューを定義する
  • ビューには public プロパティ、または Content の with: でデータを渡す
  • 送信は Mail::to()->send()。cc() / bcc() はチェーンで追加する
  • 新規プロジェクトは MAIL_MAILER=log で、メールは storage/logs/laravel.log に記録される。時刻は UTC、日本語の件名はエンコード表示、Bcc は表示されない
  • Mailable をルートから返すとブラウザでプレビューでき、render() で HTML を取得できる
  • テストは Mail::fake() と assertSent 系で行う。assertSentOnce() は Laravel 13.31 以降

関連記事

参考リンク

コメントする