desarrolla2/mail-exception-bundle
composer require symfony/mailer symfony/mime
App\Exceptions\Handler to send emails on uncaught exceptions:
// app/Exceptions/Handler.php
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\Mail;
use App\Mail\ExceptionEmail;
class Handler extends ExceptionHandler
{
public function report(Throwable $exception)
{
if (!$this->shouldSendExceptionEmail($exception)) {
return parent::report($exception);
}
Mail::to(config('mail-exception.to'))
->send(new ExceptionEmail($exception));
}
protected function shouldSendExceptionEmail(Throwable $exception): bool
{
$ignoredEnvironments = config('mail-exception.avoid.environments', []);
$ignoredExceptions = config('mail-exception.avoid.exceptions', []);
return !in_array(app()->environment(), $ignoredEnvironments)
&& !in_array(get_class($exception), $ignoredExceptions);
}
}
php artisan make:mail ExceptionEmail
// app/Mail/ExceptionEmail.php
public function build()
{
return $this->subject(config('mail-exception.subject'))
->markdown('emails.exception')
->with([
'exception' => $this->exception,
'request' => request(),
]);
}
.env:
MAIL_EXCEPTION_FROM=your@email.com
MAIL_EXCEPTION_TO=your@email.com
MAIL_EXCEPTION_SUBJECT="An error has occurred"
MAIL_EXCEPTION_AVOID_ENVIRONMENTS="dev,test"
MAIL_EXCEPTION_AVOID_EXCEPTIONS="Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException,Symfony\Component\HttpKernel\Exception\NotFoundHttpException"
php artisan vendor:publish --provider="App\Providers\MailExceptionServiceProvider"
(Create a service provider if needed to load config.)500 Internal Server Error occurs in production due to an unhandled exception.production).ExceptionHandler catches uncaught exceptions via report().report() to conditionally send emails (based on config).Mail facade to send structured emails..env or config/exception.php:
'mail-exception' => [
'from' => env('MAIL_EXCEPTION_FROM'),
'to' => env('MAIL_EXCEPTION_TO'),
'subject' => env('MAIL_EXCEPTION_SUBJECT'),
'avoid' => [
'environments' => explode(',', env('MAIL_EXCEPTION_AVOID_ENVIRONMENTS', '')),
'exceptions' => explode(',', env('MAIL_EXCEPTION_AVOID_EXCEPTIONS', '')),
],
],
dev/test environments:
if (in_array(app()->environment(), config('mail-exception.avoid.environments'))) {
return false;
}
Mail::later()) to avoid blocking requests:
Mail::queue(new ExceptionEmail($exception));
<!-- resources/views/emails/exception.blade.php -->
@component('mail::message')
# Exception Occurred: {{ $exception->getMessage() }}
**Type**: {{ get_class($exception) }}
**Stack Trace**:
```
{{ $exception->getTraceAsString() }}
```
**Request**:
- URL: {{ $request->fullUrl() }}
- Method: {{ $request->method() }}
@if($request->has('input'))
**Input**: {{ $request->input() }}
@endif
@endcomponent
Mail::to(config('mail-exception.to'))
->attach(storage_path('logs/laravel.log'))
->send(new ExceptionEmail($exception));
throttle():
use Illuminate\Cache\RateLimiter;
$limiter = RateLimiter::for('exception-emails', function () {
return Cache::forever('exception-emails');
});
if ($limiter->tooManyAttempts(1, 60)) {
return false; // Skip if >1 email/minute
}
$webhook = 'https://hooks.slack.com/...';
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'text' => "New exception: {$exception->getMessage()}",
]);
curl_exec($ch);
public function test_exception_email_is_sent()
{
$exception = new \RuntimeException('Test error');
$this->expectException(\RuntimeException::class);
Mail::fake();
$this->report($exception);
Mail::assertSent(ExceptionEmail::class);
}
Mail::fake() to verify emails are sent without hitting SMTP.Mail::queue(new ExceptionEmail($exception));
// app/Console/Commands/SendExceptionDigest.php
public function handle()
{
$exceptions = Exception::where('processed', false)->limit(50)->get();
foreach ($exceptions as $exception) {
Mail::to(config('mail-exception.to'))->send(new ExceptionDigest($exception));
$exception->update(['processed' => true]);
}
}
Double Reporting:
ExceptionHandler already logs exceptions to storage/logs/laravel.log. Ensure emails don’t duplicate efforts:
// Skip if already logged (e.g., by Monolog)
if (Log::hasErrors()) {
return false;
}
Log::channel('single') to avoid duplicate logs.Sensitive Data Leaks:
$requestData = $request->except(['password', 'api_token', 'cc_number']);
Illuminate\Support\Str::mask() for partial redaction.Environment Misconfiguration:
dev due to misconfigured .env:
APP_ENV=production # Ensure this is correct!
shouldSendExceptionEmail():
if (!in_array(app()->environment(), ['production', 'staging'])) {
return false;
}
Email Delivery Failures:
Mail::later() or a job queue (e.g., Laravel Queues + Redis).Symfony-Specific Assumptions:
ExceptionListener. Laravel’s ExceptionHandler works differently:
KernelEvents::EXCEPTION.report()/render() in App\Exceptions\Handler.php artisan queue:work
php artisan
How can I help you explore Laravel packages today?