Search code examples
phplaravelfirebasenotificationsqueue

Send large number of notifications to users - Laravel


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


Solution

  • 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()));
            }