DEV Community

Cover image for Architecting Hyper-Local Push Notifications in Laravel 11 🌩️
Prajapati Paresh
Prajapati Paresh

Posted on Originally published at smarttechdevs.in

Architecting Hyper-Local Push Notifications in Laravel 11 🌩️

The Communication Imperative in AgTech

In agriculture, timely information is not just a convenience; it dictates financial survival. One of the most devastating events for an Indian farmer is unseasonal rain (કમોસમી વરસાદ / માવઠું) during harvest season. If a farmer receives a 24-hour warning, they can deploy tarpaulins and save their entire year's income. If they do not, the crop rots in the field.

When architecting KhedutBandhu, we knew that waiting for a user to open the app and check the weather was insufficient. We had to architect a proactive, push-based communication layer. However, broadcasting a mass warning to 100,000 farmers across the entire state when the storm is only hitting a 50-kilometer radius in Rajkot causes "Alert Fatigue." Users will assume the app is inaccurate and turn off notifications entirely.

At Smart Tech Devs, we engineered a Hyper-Local Asynchronous Dispatch Engine using Laravel Task Scheduling, chunked database querying, and Firebase Cloud Messaging (FCM) to deliver mathematically precise agronomic warnings directly to the mobile lock screen.

Phase 1: The Meteorological Ingestion Cron

The architecture begins with a scheduled Laravel command that continuously ingests meteorological data. Every 15 minutes, the system polls government or premium weather APIs, searching specifically for severe weather anomalies (heavy rain, extreme frost, or high wind speeds) mapped to specific geographical bounding boxes.


namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\WeatherApi;
use App\Jobs\DispatchHyperLocalAlerts;

class IngestWeatherAlerts extends Command
{
    protected $signature = 'weather:scan-anomalies';

    public function handle(WeatherApi $weather)
    {
        // 1. Fetch active severe weather polygons from the meteorological API
        $activeStorms = $weather->getSevereAnomalies();

        foreach ($activeStorms as $storm) {
            // 2. If a dangerous anomaly is detected (e.g., Unseasonal Rain)
            if ($storm->type === 'unseasonal_rain' && $storm->severity === 'high') {
                
                // 3. Dispatch the localized alerting job immediately
                DispatchHyperLocalAlerts::dispatch($storm->polygon_wkt, $storm->message);
            }
        }
    }
}

Phase 2: Chunking the Spatial Database

The heavy lifting occurs in the background queue. We must query our database to find every single farmer whose registered land falls inside the specific storm's geographical polygon.

If KhedutBandhu scales to 500,000 users, loading 50,000 affected users into memory simultaneously to send push notifications will instantly crash the Laravel Queue worker with a Allowed memory size exhausted fatal error. We must architect this using Laravel's chunkById() combined with PostGIS spatial indexing to process users in highly controlled, memory-safe batches.


namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use App\Notifications\SevereWeatherAlert;

class DispatchHyperLocalAlerts implements ShouldQueue
{
    use Dispatchable, Queueable;

    public function __construct(
        public string $stormPolygonWkt, 
        public string $alertMessage
    ) {}

    public function handle()
    {
        // 1. We use chunkById to fetch exactly 500 users at a time.
        // This guarantees stable RAM consumption regardless of how many users are affected.
        User::query()
            ->whereNotNull('fcm_token')
            // 2. Spatial Query: Find users whose farm point is inside the storm polygon
            ->whereRaw("ST_Intersects(farm_location, ST_GeogFromText(?))", [$this->stormPolygonWkt])
            ->chunkById(500, function ($users) {
                
                // 3. Process the safe batch of 500 users
                $fcmTokens = $users->pluck('fcm_token')->toArray();
                
                // 4. Dispatch to Firebase via a specialized Bulk Service
                app(\App\Services\FirebaseService::class)->sendBulkNotification(
                    $fcmTokens,
                    'કમોસમી વરસાદની ચેતવણી (Weather Alert)',
                    $this->alertMessage
                );
            });
    }
}

Phase 3: Bulk Dispatching via Firebase (FCM)

Sending 500 individual HTTP requests to Firebase in a loop is an architectural anti-pattern. Network latency will back up the queue worker. We leverage the Firebase Cloud Messaging Multicast API, allowing us to send a single JSON payload containing all 500 device tokens to Google's servers in one rapid, 50-millisecond network hop.


namespace App\Services;

use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;
use Kreait\Firebase\Contract\Messaging;

class FirebaseService
{
    public function __construct(private Messaging $messaging) {}

    public function sendBulkNotification(array $tokens, string $title, string $body): void
    {
        $notification = Notification::create($title, $body);

        $message = CloudMessage::new()->withNotification($notification);

        // This single API call pushes the alert to up to 500 mobile devices instantly
        $report = $this->messaging->sendMulticast($message, $tokens);

        if ($report->hasFailures()) {
            // Handle dead tokens (users who uninstalled the app) to clean the DB
            $this->cleanupDeadTokens($report->failures());
        }
    }
}

The Engineering ROI and Societal Impact

By architecting our alert system using Laravel Task Scheduling, PostGIS spatial intersects, and Firebase Multicast chunking, KhedutBandhu achieves unparalleled, targeted communication stability. The infrastructure guarantees that CPU and RAM utilization remain perfectly flat, even if a massive monsoon sweeps across the entire state. We prevent alert fatigue by ensuring farmers only receive warnings mathematically relevant to their specific GPS coordinates. Most importantly, this hyper-local, event-driven architecture bridges the gap between raw meteorological data and real-world agricultural action, directly protecting farmers' livelihoods from catastrophic weather events.

Top comments (0)