Abdes
Abdes

Reputation: 996

Laravel: Send 1000 emails every hour using queue

I have a table users with 5000 records (5000 users) and in my server i can just send 1000 emails every hour. How i can send 1000 emails every hour using queue ? or how to make queues sleep inside loop?

EmailController:

class EmailController extends Controller
{
    public function sendEmail(Request $request){

         $event=(object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];
         $users=User::all();
         App\Jobs\SendReminderEmail::dispatch($users,$event)
    }
}

SendReminderEmail

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

    public $event;

    public $email;

    public $users;

    public function __construct($users,$event)
    {
        $this->users = $users;

        $this->event = $event;
    }

    public function handle()
    {   
        foreach ($this->users as $user) {

         Mail::to($user->email)->queue(new Reminder($this->event));
        }
    }
}

Upvotes: 3

Views: 3858

Answers (2)

Travis Britz
Travis Britz

Reputation: 5552

Laravel has a built-in throttle feature for rate-limited jobs. From the docs:

If your application interacts with Redis, you may throttle your queued jobs by time or concurrency. This feature can be of assistance when your queued jobs are interacting with APIs that are also rate limited. For example, using the throttle method, you may throttle a given type of job to only run 10 times every 60 seconds. If a lock can not be obtained, you should typically release the job back onto the queue so it can be retried later:

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

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

In your case, that might look like Redis::throttle(...)->allow(1000)->every(3600)->...

If you're not using Redis, another possible solution which is specific to queued mail is to add a delay. Again, from the docs:

Delayed Message Queueing

If you wish to delay the delivery of a queued email message, you may use the later method. As its first argument, the later method accepts a DateTime instance indicating when the message should be sent:

$when = now()->addMinutes(10);

Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->later($when, new OrderShipped($order));

Using this method would require you to calculate the delay for each email during the loop that dispatches emails to the queue. For example, every 1000th iteration of the loop you could increase the delay by 1 hour.

If you're using the Amazon SQS queue service, the same maximum delay of 15 minutes that applies to other queued jobs might also apply here (I'm not sure on this part), in which case you would have to come up with another solution for checking if you're over your rate limit and releasing the job back to the queue.

Upvotes: 1

Asur
Asur

Reputation: 4017

Laravel has a neat feature which fits your case perfectly, it's called Task Scheduling, instead of making the job sleep for one hour, you could, instead, call it every hour.

To do so add the job schedule to the schedule() method located on App\Console\Kernel like this:

protected function schedule(Schedule $schedule)
{
    $schedule->job(new SendReminderEmail)->hourly();
}

I also would recommend you to make the job self contained, that will make this task much simpler, I'm thinking in something like this:

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

    public $event;

    public $users;

    public function __construct()
    {
        $this->users = User::all();

        $this->event = (object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];;
    }

    public function handle()
    {   
        foreach ($this->users as $user) {
            Mail::to($user->email)->queue(new Reminder($this->event));
        }
    }
}

Now you can just get rid of your controller because this job will be executed every hour automatically.

Remember that you will need to run a cron on your server to check if the job needs to be executed. You can also run it manually if you want to test it using php artisan schedule:run.

Hope this helps you.

Upvotes: 1

Related Questions