简体   繁体   中英

How to rate limit queued verification and password reset notification emails in laravel 5.7

Issue

Using Laravel 5.7 with Redis, I have gotten the email verification and password reset notifications to queue using the method described in Stephen Mudere's answer in How to send the password reset link via email using queue in laravel 5 , but I cannot figure out how to rate-limit those particular queued notifications. Because my application will send emails for a variety of reasons (not just these two purposes) and my email server is rate-limited to 30 emails per minute, I need to rate-limit everything on an 'email' queue.

Background

Per the Laravel queue documentation , it seems fairly straighforward to do this in a job class in the handle method using

Redis::throttle('key')->allow(10)->every(60)->then(function () {
  // Job logic...
}, function () {
  // Could not obtain lock...

  return $this->release(10);
});

The problem is that I'm not using a job class, but rather a notification. For example, for the password reset, I've created the following

ResetPassword Class
namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;

class ResetPassword extends ResetPasswordNotification implements ShouldQueue
{
    use Queueable;
}

which gets invoked from the User model using:

public function sendPasswordResetNotification($token)
{
        $this->notify(new ResetPasswordNotification($token));
}

Approach

I have attempted to solve this by modifying the sendPasswordResetNotification function in the User model:

public function sendPasswordResetNotification($token)
{
    Redis::throttle('email')->allow(2)->every(60)->then(function () use($token) {
        $this->notify(new ResetPasswordNotification($token));
    }, function () {
        // Could not obtain lock...

        return $this->release(10);
    });
}

Note that the values for the throttle are artificially low for testing purposes. This seems to work partially. In the example above, if I try two successive password resets, the emails both queue and send. When I try to send the third email (exceeding the limit of 2 per minute I've set), I get a BadMethodCallException, "Call to undefined method App\\User::release()" . I understand that this is because the User model doesn't have a release method, but it comes back to the problem that I'm not sure exactly where or how to use the throttling logic. Is there a way to modify this to work, or do I need to take a totally different approach to sending these messages?

Update: Alternative approach that fails for a different reason

I switched from using a notification to using a Job so that I could use the Redis::throttle per the documentation. To set up the job to queue a message, I used the approach from How to queue Laravel 5.7 "email verification" email sending . This worked fine for sending queued emails. I then tried to throttle the jobs going onto the queue. Here's my complete approach:

use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Support\Facades\Redis;

class QueuedVerifyEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function handle()
    {
        Redis::throttle('email')->allow(2)->every(60)->then(function () {
            $this->user->notify(new VerifyEmail);
        }, function() {
           return $this->release(10);
        });
    }
}

These go onto the queue, but then fail. In the stack trace is the following: Symfony\\Component\\Debug\\Exception\\FatalThrowableError: Class 'App\\Jobs\\Redis' not found in /home/vagrant/code/myapp/app/Jobs/QueuedVerifyEmail.php:27

I can't figure out why it's looking for the Redis facade in App\\Jobs when I have a use statement to define the correct location.

I got it working

The solution under "Update: Alternative approach" ended up working. I'm not sure why it was failing (perhaps something was cached?), but it now appears to work correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM