[ GUIDE ]

How to Monitor Laravel Mail in Production

Delivery rate, send latency, failed sends — and how to find the difference between "Laravel didn't send it" and "the provider bounced it."

QUICK ANSWER

How do I monitor Laravel email sending in production?

Monitor three layers: your mail provider's dashboard for delivery / bounce / spam status, Laravel's MessageSent and MessageFailed events to log every attempt, and an APM mail watcher that ties sends to the request or job that triggered them with per-class duration and failure stack traces.

Updated · 2026-04-13

Always queue mail in production

Synchronous sends block the request for 100-2000ms per email. Queue it.

php
// Bad — blocks the signup request
Mail::to($user)->send(new WelcomeMail($user));

// Good — dispatches to a queue worker
Mail::to($user)->queue(new WelcomeMail($user));

// Better — delayed so it feels less bot-like
Mail::to($user)->later(now()->addMinutes(2), new WelcomeMail($user));

If your mail worker isn't running, queued mail silently piles up. See our queue monitoring guide.

Log every send and failure

Laravel fires events on every mail operation.

app/Providers/AppServiceProvider.phpphp
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(MessageSent::class, function (MessageSent $event) {
        \DB::table('mail_log')->insert([
            'subject' => $event->message->getSubject(),
            'to' => collect($event->message->getTo())->map->getAddress()->implode(', '),
            'mailer' => $event->data['__laravel_notification_mailer'] ?? 'default',
            'sent_at' => now(),
        ]);
    });
}

Mail provider webhooks fill in what happens after the send — delivered, bounced, complained, opened. Register a webhook endpoint with your provider and log those too.

Track three metrics

SEND RATE

Mails sent per minute by class. A sudden drop usually means the queue worker died or SMTP credentials expired.

SEND DURATION

p95 time from dispatch to SMTP accept. Spikes usually mean provider rate limits or network issues.

DELIVERY RATE

Delivered / attempted from provider webhooks. A drop usually means IP reputation issues or bad recipient lists.

What to alert on

  • Send rate drops to 0 for >5 minutes — worker down or credentials bad
  • Spike in MessageFailed — 20+ failures of the same Mailable class in 10 min
  • Bounce rate > 2% — list hygiene problem, risking IP reputation
  • Complaint rate > 0.1% — users marking as spam, provider will soon throttle you

Route mail alerts to Slack or Discord, never email — if mail is broken, email alerts are exactly what you can't receive.

THE EASY WAY

NightOwl logs every mail send with full context

NightOwl's mail watcher records every send — Mailable class, to/from, subject, mailer, duration, and any thrown exception — tied to the request or queued job that triggered it. Grouped per Mailable class so you can see p95 send duration and failure rate over any time range.

bash
composer require nightowl/agent
php artisan nightowl:install

Frequently asked questions

How do I know if my Laravel app is actually sending emails?

Three places to check: (1) your mail provider's dashboard (Postmark, SES, Mailgun, Resend) shows delivered/bounced/opened, (2) Laravel's MessageSent and MessageFailed events in AppServiceProvider let you log attempts, (3) an APM with a mail watcher records every send with class, recipient, duration, and exception — tying mail sends back to the request or job that triggered them.

Why did my Laravel email not arrive?

Five common causes ranked: (1) the queued mail job failed silently — check failed_jobs, (2) the SMTP credentials are wrong or expired — check your provider dashboard, (3) the mail was sent but bounced or was spam-filtered — check provider delivery log, (4) MAIL_MAILER=log is set in production, (5) the Mailable has a missing blade view that threw server-side but was caught.

Should I use Mail::queue or Mail::send?

Mail::queue in production, always. Synchronous sends block the request for 100-2000ms per email — unacceptable on a signup or checkout. Queue dispatches to a background worker that retries on failure and doesn't delay user response. The only reason to use synchronous send is in a CLI script where you can block.

How do I log all outbound Laravel email for audit?

Listen to the MessageSent event and persist to a table: message class, to/from/subject, sent_at, user_id. For compliance (HIPAA, financial) keep this in a separate table you control the retention of. Most mail providers also offer message archive — use both: provider for delivery status, your own for audit.

Can I test Laravel emails without sending them?

In tests, use Mail::fake() to intercept sends and assert what would have gone out. In development, set MAIL_MAILER=log to write to storage/logs/laravel.log, or MAIL_MAILER=array to collect in memory. For a realistic preview, use Mailtrap or similar — catches every email, shows rendering.

How do I monitor Laravel email latency and delivery rate?

Time the send duration in a MessageSent listener and log it with class + mailer. Track delivery rate via your provider's webhook (bounces, spam complaints, delivered). An APM with mail watcher (NightOwl, Nightwatch Cloud) ties sends to requests, shows per-class duration percentiles, and surfaces mail failures as exceptions.

Should I alert on mail send failures?

Yes, with thresholds. A single failed send is noise (network blip, one bad address). A spike — 20+ failures in 10 minutes of a specific Mailable class — is a real signal. Route alerts to Slack or Discord, not email (you might be having mail issues!).

PRICING

Flat pricing. No event caps. No per-seat fees.

14-day free trial, no credit card. Your PostgreSQL, your data.

HOBBY

$5 /month

1 app · 14 days lookback · all Laravel events

TEAM

$15 /month

Up to 3 connected apps · unlimited environments · all Laravel events

AGENCY

$69 /month

Unlimited apps · unlimited agent instances · same flat rate at any traffic

Related