I want to send a large number of notifications in Laravel.
The notification method is firebase and I'm using kutia-software-company/larafirebase package as Notifiable.
Currently, I have about 10000 fcms but I want to optimize sending notifications to 100000 fcms.
I implemented a system like below:
First, I created a notifiable class as described in the package
<?php
namespace Vendor\Core\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Kutia\Larafirebase\Messages\FirebaseMessage;
class FirebasePushNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(protected $title, protected $message, protected array $fcmTokens = [])
{
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['firebase'];
}
public function toFirebase($notifiable)
{
return (new FirebaseMessage)
->withTitle($this->title)
->withBody($this->message)
->withPriority('high')
->asNotification($this->fcmTokens);
}
}
Second, Created a Job for notifications:
<?php
namespace Vendor\PushNotification\Jobs;
use Vendor\Core\Notifications\FirebasePushNotification;
use Vendor\Customer\Contracts\UserDeviceInfo;
use Vendor\Customer\Contracts\Customer;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Notification;
class SendPushNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;
public function __construct(public string $title, public string $body, public Customer $customer, public UserDeviceInfo $userDeviceInfo)
{
}
public function handle()
{
Notification::send($this->customer, new FirebasePushNotification($this->title, $this->body, [$this->userDeviceInfo->fcm]));
}
}
And using Bus::dispatch
for sending jobs to queue:
$usersDeviceInfos = $usersDeviceInfos->with('customer')->get();
$bus = [];
for ($i = 0; $i < 100000; $i++){
foreach ($usersDeviceInfos as $usersDeviceInfo) {
$bus [] = new SendPushNotificationJob($data['notification_title'], $data['notification_body'], $usersDeviceInfo->customer, $usersDeviceInfo);
}
}
Bus::batch($bus)->name('push notification' . now()->toString())->dispatch();
Currenly, because of development environement I have just one fcm and use a loop to simulate 10000, It takes more than a minute to run this code and I'll get Maximum execution error.
Also, note that I configured my queue and it's not sync
and I checked these two questions but didn't help:
Hope to find a faster method to batch those jobs and send them to queue
I found a solution that can handle sending notifications up to one million fcms in a few seconds
I decided to use kreait/laravel-firebase
and use multicast that was mentioned here
First I chunk fcms then I use A job and dispatch them to the queue.
Here's an example of the solution:
<?php
namespace Vendor\PushNotification\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Laravel\Firebase\Facades\Firebase;
class SendPushNotificationJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Batchable;
public function __construct(public string $title, public string $body, public array $fcmTokens)
{
}
public function handle()
{
$message = CloudMessage::fromArray(YOUR_DATA);
Firebase::messaging()->sendMulticast($message, $this->fcmTokens);
}
}
$chunks = $usersDeviceInfos->with('customer')->get()->pluck('fcm')->chunk(500);
foreach ($chunks as $chunk) {
dispatch(new SendPushNotificationJob($data['notification_title'], $data['notification_body'], $chunk->toArray()));
}