Always queue mail in production
Synchronous sends block the request for 100-2000ms per email. Queue it.
// 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.
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.
composer require nightowl/agent
php artisan nightowl:install