spatie/laravel-failed-job-monitor
Laravel package that notifies you when queued jobs fail. Uses Laravel’s notification system and supports email and Slack out of the box, with configurable notifiables and custom notification classes for your own alerting needs.
Install the package:
composer require spatie/laravel-failed-job-monitor
For Slack support:
composer require guzzlehttp/guzzle
Publish the config:
php artisan vendor:publish --tag=failed-job-monitor-config
Configure .env:
FAILED_JOB_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
MAIL_MAILER=smtp
MAIL_FROM_ADDRESS="noreply@example.com"
Verify channels in config/failed-job-monitor.php:
'channels' => ['mail', 'slack'], // or ['mail'] if Slack is unused
'mail' => [
'to' => ['admin@example.com'],
],
Trigger a test failure:
// In a job class
public function handle()
{
throw new \Exception("Test failure for monitoring");
}
Dispatch it:
php artisan queue:work
Check your email/Slack for the notification.
Job Failure Handling:
The package hooks into Laravel’s FailedJob event. When a job fails, it automatically triggers the configured notification.
Notification Customization:
// config/failed-job-monitor.php
'notification' => \App\Notifications\CustomFailedJobNotification::class,
Example custom notification:
namespace App\Notifications;
use Spatie\FailedJobMonitor\Notification as BaseNotification;
class CustomFailedJobNotification extends BaseNotification
{
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Job Failed: ' . $this->job->name)
->line('Job ID: ' . $this->job->id)
->action('View in Horizon', url('/horizon/jobs/' . $this->job->id));
}
}
Dynamic Recipients: Use environment variables for flexibility:
FAILED_JOB_MONITOR_MAIL_TO=admin@example.com,backup@example.com
FAILED_JOB_MONITOR_SLACK_WEBHOOK_URL=https://...
Update config/failed-job-monitor.php to read from env:
'mail' => [
'to' => explode(',', env('FAILED_JOB_MONITOR_MAIL_TO', 'admin@example.com')),
],
'slack' => [
'webhook_url' => env('FAILED_JOB_MONITOR_SLACK_WEBHOOK_URL'),
],
Filtering Failures: Skip notifications for specific jobs or exceptions:
// config/failed-job-monitor.php
'notificationFilter' => [\App\Filters\FailedJobFilter::class, 'shouldNotify'],
Example filter:
namespace App\Filters;
use Spatie\FailedJobMonitor\Notification;
class FailedJobFilter
{
public static function shouldNotify(Notification $notification): bool
{
return !str_contains($notification->job->name, 'SendEmail');
}
}
Integration with Horizon: Add a link to Horizon in notifications (if using Horizon):
// In your custom notification
->action('View in Horizon', config('horizon.path') . '/jobs/' . $this->job->id)
Multi-Channel Notifications: Combine email and Slack dynamically:
'channels' => ['mail', 'slack'],
Or conditionally:
'notificationFilter' => function (Notification $notification) {
return app()->environment('production') ? true : false;
},
Job-Specific Notifications: Attach metadata to jobs and use it in notifications:
// In your job
public function handle()
{
$this->job->payload['custom_data'] = ['user_id' => 123];
// ...
}
Access in notification:
$this->job->payload['custom_data']['user_id']
Rate Limiting: Avoid notification spam by throttling:
// In your custom notification
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\RateLimiter;
public function toMail($notifiable)
{
if (RateLimiter::tooManyAttempts($notifiable->getKeyName(), 5)) {
return null; // Skip notification
}
return (new MailMessage)->line('Job failed!');
}
Config Caching Issues:
config/failed-job-monitor.php, clear the config cache:
php artisan config:clear
notificationFilter (use static methods instead to avoid serialization errors).Horizon Compatibility:
horizon.path config is set in config/horizon.php:
'path' => 'horizon',
Slack Webhook Validation:
Job Payload Serialization:
Queue Worker Restarts:
php artisan queue:restart
tail -f storage/logs/laravel.log
php artisan tinker
>> \Illuminate\Support\Facades\DB::table('failed_jobs')->latest()->first();
\Log::debug('Failed job notification triggered', [
'job' => $this->job,
'exception' => $this->exception,
]);
Custom Notifiable:
Extend \Spatie\FailedJobMonitor\Notifiable to add new channels (e.g., PagerDuty, SMS):
namespace App\Notifiables;
use Spatie\FailedJobMonitor\Notifiable as BaseNotifiable;
class CustomNotifiable extends BaseNotifiable
{
public function routeNotificationForPagerDuty()
{
return 'your-pagerduty-integration-key';
}
}
Update config:
'notifiable' => \App\Notifiables\CustomNotifiable::class,
Dynamic Notification Data: Pass additional context to notifications:
// In your job's handle() method
app(\Spatie\FailedJobMonitor\FailedJobMonitor::class)
->notifyWith(['custom_key' => 'custom_value']);
Access in notification:
$this->customData['custom_key']
Retry Logic:
Combine with shouldRetry in jobs to avoid duplicate notifications:
public function shouldRetry()
{
return false; // Disable retries to ensure notification is sent
}
Environment-Specific Config: Use Laravel’s config caching with environment-specific files:
cp config/failed-job-monitor.php config/failed-job-monitor-local.php
Update config/app.php:
'config' => [
'failed-job-monitor' => env('APP_ENV') === 'local'
? __DIR__ . '/failed-job-monitor-local.php'
: __DIR__ . '/failed-job-monitor.php',
],
toMail).// In your custom notification logic
dispatch(new SendFailedJobNotification($notifiable, $this));
How can I help you explore Laravel packages today?