Reputation: 996
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
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, thelater
method accepts aDateTime
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
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