Self-Dispatching Laravel Queues: Orchestrating a Telegram Bingo Bot
Overview
Managing real-time game logic requires a delicate balance between immediate response and scheduled automation. This implementation uses
Prerequisites
To implement this pattern, you should be comfortable with:
- Laravel Framework: Basic understanding of Services and Controllers.
- Queue Workers: Knowledge of how to run
php artisan queue:work. - Telegram Bot API: Familiarity with webhooks and command handling.
- Database Management: Experience with migrations and basic CRUD operations.
Key Libraries & Tools
- Laravel Queues: The core engine for background job execution.
- Filament Admin Panel: An admin panel used to trigger game sessions and configure parameters.
- Laravel Forge: Used for server management and maintaining the queue worker process.
- Database Driver: The chosen queue driver for persistence and reliability.
Code Walkthrough

The Initial Delay
When a game starts, we dispatch a job to close the registration window after a specific interval.
// In GameLifeCycleService.php
CompleteJoinPeriod::dispatch($game)->delay(now()->addSeconds($game->join_seconds));
This line instructs the queue to wait exactly $X$ seconds before moving the game from 'joining' to 'active' status.
Recursive Self-Dispatching
Once the game is active, the DrawNumber job handles the logic of pulling a number and then queues its own successor.
public function handle()
{
// 1. Draw and notify
$this->drawAndSendMessage();
// 2. Check termination condition
if ($this->game->draws->count() >= 75) {
return; // Stop the cycle
}
// 3. Self-dispatch with delay
self::dispatch($this->game->fresh())
->delay(now()->addSeconds(5));
}
By calling self::dispatch() within the handle method, the job creates a loop. It executes, waits 5 seconds, and executes again until 75 numbers are drawn or a player claims bingo.
Syntax Notes
- Fresh Models: Always use
$model->fresh()when dispatching to ensure the next job has the most recent database state. - Delay Method: The
delay()helper accepts aDateTimeorCarboninstance to set execution timing. - Termination Returns: Simply returning from the
handlemethod without dispatching prevents the next cycle from starting.
Practical Examples
- Auction Countdowns: Triggering a "Going once, going twice" sequence after a bid.
- Drip Campaigns: Sending a series of onboarding emails spaced 24 hours apart.
- Status Monitoring: Checking an external API status every minute until a specific result returns.
Tips & Gotchas
- Queue Workers: Ensure your queue worker is running as a daemon. On Laravel Forge, set up a process to keep the worker active.
- Database Locks: When multiple jobs or webhooks might update the same game record (like a
/bingocommand during a draw), uselockForUpdate()to prevent race conditions. - Memory Leaks: Since these jobs can loop many times, avoid heavy static caching within the job class.

Fancy watching it?
Watch the full video and context