Developer Guides

Testing Email in Laravel Applications

Test Laravel mailables and notifications with Mail::fake() and Notification::fake() so CI never sends real email. Assert recipients, subjects, and queued status safely.

Published

Published by Arawa Mail.

Testing Email in Laravel Applications

Use Mail::fake() and Notification::fake() in Laravel tests so mailables and mail-channel notifications are recorded but never leave your CI environment. Assert recipients, subjects, and queued versus sent status with the built-in assertion methods; keep live API keys out of the test suite.

Copying a working send configuration into PHPUnit or Pest is the fastest way to email a real customer from GitHub Actions. The fix is simple: fake the mail and notification systems before any code under test runs.

Will MAIL_MAILER=sdp send real mail during tests?

Yes—if you do not fake Mail first. The Arawa Mail transport (package sharasolns/sdp-email) uses the same Laravel mailer contract as SMTP or other drivers. Setting MAIL_MAILER=sdp and providing SDP_EMAIL_KEY causes a real HTTP request once the transport is invoked. Fakes intercept the call before the transport runs, so no key is required in the test environment.

There is no documented test-mode flag or sandbox send endpoint in the Arawa Mail API. Treat every unfaked send as a live delivery.

Mail::fake() for mailables

Call Mail::fake() at the start of the test (or in a setUp method). After the code under test runs, use the assertion helpers:

use Illuminate\Support\Facades\Mail;
use App\Mail\OrderShipped;

// This fragment runs inside a feature test; $user is a test fixture.
Mail::fake();

// ... perform the action that should send mail ...

Mail::assertSent(OrderShipped::class, function ($mail) use ($user) {
    return $mail->hasTo($user->email);
});

Mail::assertSentCount(1);
// Put Mail::assertNothingSent() in a separate test for a no-send case.

Useful assertions include assertSent, assertNotSent, assertNothingSent, assertSentCount, and assertSentTimes. Closures let you inspect the mailable instance for recipients, subject, and view data.

Queued mailables

When the mailable implements ShouldQueue (or you call ->queue()), use the queued assertions instead of (or in addition to) the sent ones:

Mail::assertQueued(OrderShipped::class);
Mail::assertQueuedCount(1);
Mail::assertOutgoingCount(1); // sent + queued combined

You do not need a running queue worker; the fake records the job without dispatching it. For production queue behaviour see Queue Laravel Mailables with ShouldQueue.

Notification::fake() for mail-channel notifications

Mail-channel notifications are asserted on the Notification fake, not only on Mail:

use Illuminate\Support\Facades\Notification;
use App\Notifications\InvoicePaid;

Notification::fake();

// ... action that notifies the user ...

Notification::assertSentTo($user, InvoicePaid::class);
// In a separate no-notification test: Notification::assertNothingSent();

If your code uses both mailables and notifications, fake both facades.

Pest versus PHPUnit

The same helpers work in both styles. Pest example:

it('sends an order shipped email', function () {
    Mail::fake();
    $user = User::factory()->create();

    // Trigger the application action that sends OrderShipped to this user.

    Mail::assertSent(OrderShipped::class, fn ($mail) => $mail->hasTo($user->email));
});

When a real send is acceptable

  • A single staging-only smoke test after the domain is enabled for outbound sending.
  • Never in the default CI job that runs on every pull request.
  • Never with a production API key.

Faked tests do not create Sent-folder rows in Arawa Mail; only a successful real send does.

CI hygiene checklist

  • Call Mail::fake() (and Notification::fake() when needed) before any mail-related code.
  • Do not set SDP_EMAIL_KEY in the test environment variables.
  • Assert the expected recipients and, when relevant, the subject or view data.
  • Prefer assertQueued for background mail so the test stays independent of workers.
  • Keep live integration tests behind an explicit environment flag that is off by default.

For the initial send setup, follow the published guide Send email in Laravel and the official Laravel quickstart. For raw messages see Laravel Mail::raw; for the choice between transports see SMTP vs Email API.

FAQ

Should I fake Mail, Notification, or both?

Fake Mail when the code under test uses the Mail facade or mailables. Fake Notification when it uses the Notification system (including mail-channel notifications). Many feature tests need both.

Does faking create a Sent-folder row?

No. Only a successful real API send writes to the Sent folder in Arawa Mail.

How do I assert a queued mailable without running a worker?

Use Mail::assertQueued() or Mail::assertQueuedCount(). The fake records the job without executing it.

What happens if the From domain is not registered?

A real send is rejected. Faked tests never reach the transport, so the rejection does not occur in unit or feature tests that properly fake Mail.

Sources and review

Reviewed against the linked documentation on 3 October 2026. Examples are illustrative; code that depends on application models must be adapted and tested in your own project.

Simple, transparent plans

Start free. Grow when your email does.

Get one domain, API access and 3,000 transactional emails every month at no cost.

Compare plans