Search code examples
phpdatabaselaravelemailswiftmailer

Throttle Laravel Exception email notification per Exception type


Does anyone know of a way to throttle email notifications for a certain Exception in Laravel?

I made my app send me an email when there is a DB error by check for the QueryException. This is a rough example of what I did in Exception handler in Laravel:

class Handler{

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $exception
     * @return void
     */
    public function report(Exception $e)
    {
        if($e instanceof QueryException){

            if( App::environment(['production']) ){
                Notification::route('mail', 'myemail@test.com')
                            ->notify(new DbErrorNotification($e));
            }
        }

        parent::report($e);
    }

}

Short of tracking in DB, is there a way I could throttle DB errors by exception type so that I don't end up getting thousands of emails if there is a consistent DB error.

I looked at Swift Mailer's anti-flood and throttling plugins but those affect the system globally which I don't want to do.

Thank you in advance


Solution

  • There is a few way how you can achieve that. Right before dispatching a job you can add delay on it. Example:

    use App\Http\Request;
    use App\Jobs\SendEmail;
    use App\Mail\VerifyEmail;
    use Carbon\Carbon;
    
    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     */
    public function store(Request $request)
    {
        $baseDelay = json_encode(now());
    
        $getDelay = json_encode(
            cache('jobs.' . SendEmail::class, $baseDelay)
        );
    
        $setDelay = Carbon::parse(
            $getDelay->date
        )->addSeconds(10);
    
        cache([
            'jobs.' . SendEmail::class => json_encode($setDelay)
        ], 5);
        SendEmail::dispatch($user, new VerifyEmail($user))
             ->delay($setDelayTime);
    }
    

    Or if you don't like the idea about a job you can also delay it via Mail. Example:

    Mail::to($user)->later($setDelayTime);
    

    And finally via Redis Rate Limiting. Example:

    use Illuminate\Support\Facades\Mail;
    use Illuminate\Support\Facades\Redis;
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        Redis::throttle('SendEmail')
            ->allow(1)
            ->every(10)
            ->then(function () {
                Mail::to($this->user)->send($this->mail);
            }, function () {
                return $this->release(10);
            });
    }
    

    Allowing one email to be sent every ten seconds. The string SendEmail passed to the throttle() method is a name that uniquely identifies the type of job being rate-limited. You can set this to whatever you want.

    The release() method is an inherited member of your job class and instructs Laravel to release the job back onto the queue, with an optional delay in seconds, in the event a lock cannot be obtained. When the job is dispatched to the queue, Redis is instructed to only run one SendEmail job every ten seconds.

    Keep in mind that for all of this you need a Redis

    Source: https://medium.com/@bastones/a-simple-guide-to-queuing-mail-in-laravel-f4ff94cdaa59