Laravel - Running Jobs in Sequence
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
# Laravel - Running Jobs in Sequence: Achieving Guaranteed Order
As developers working with Laravel, especially when leveraging powerful queue systems like Horizon, we often deal with the complexity of asynchronous processing. The beauty of queues is their ability to handle high volumes of tasks concurrently, which significantly improves application responsiveness and scalability. However, as you've encountered, this concurrency introduces a challenge: ensuring that specific operationsâlike sending emails or updating records in a strict orderâare executed one after the other.
The scenario you describedâdispatching jobs that run across multiple workers rather than sequentiallyâis perfectly normal when using default queue configurations. To solve this, we need to move beyond simple job dispatching and implement a mechanism for **job chaining**.
## Understanding the Concurrency Problem
When you use `SendMailJob::dispatch($subscription)` inside your controller method, Laravel places that job onto the queue. Your Horizon workers (or any queue worker) pull these jobs off the queue as fast as they become available. If you have ten subscriptions, ten workers can simultaneously pick up and process those ten jobs at the same time, leading to non-deterministic execution order.
To enforce a strict sequenceâwhere Job B *must* start only after Job A has successfully completedâwe need to explicitly link them in a dependency chain rather than treating them as independent tasks.
## The Solution: Implementing Job Chaining
The most idiomatic and robust way to ensure sequential execution in Laravel is by chaining jobs together using the `then()` method. This mechanism tells the queue system that the next job should only be dispatched once the preceding job has successfully finished processing.
### Step-by-Step Implementation
Instead of dispatching jobs individually, we will design our workflow so that one job triggers the next. This is often best handled by having the first job handle the data retrieval and then chain the subsequent actions.
Here is how you can refactor your process to ensure sequential execution:
#### 1. Refactor the Job
Let's assume you have a primary job that handles the initial task, and subsequent jobs are linked from within it.
```php
// app/Jobs/SendMailJob.php
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendMailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $subscription;
public function __construct($subscription)
{
$this->subscription = $subscription;
}
/**
* Execute the job.
*/
public function handle()
{
// Step 1: Perform the initial action (e.g., sending an email)
\Log::info("Starting mail process for subscription ID: {$this->subscription->id}");
// Step 2: Crucially, chain the next job using then()
$this->chainToNextJob();
}
/**
* Method to chain the next job in the sequence.
*/
protected function chainToNextJob()
{
// Dispatch the next job only after this one completes successfully.
SendNextSubscriptionJob::dispatch($this->subscription)
->onQueue('emails'); // Optional: Use a specific queue for ordering if needed
}
}
```
#### 2. Create the Subsequent Job
We now create the job that should run immediately after the first one finishes.
```php
// app/Jobs/SendNextSubscriptionJob.php
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
// ... other imports
class SendNextSubscriptionJob implements ShouldQueue
{
// ... properties and constructor
public function handle()
{
// Step 3: Perform the next action
\Log::info("Successfully completed mail process for subscription ID: {$this->subscription->id}. Proceeding to next step.");
// If there were more steps, you would chain again here.
}
}
```
### Why This Works (And Laravel Context)
By using `dispatch()->then(...)`, you are leveraging the underlying queue infrastructure's promise mechanism. The system is instructed that the execution of `SendNextSubscriptionJob` is dependent on the successful completion of `SendMailJob`. This effectively forces sequential processing within the context of the queue workers, solving your ordering problem while still benefiting from Laravelâs powerful queuing features (as discussed in resources like those found at [https://laravelcompany.com](https://laravelcompany.com)).
## Conclusion
For scenarios where strict ordering is paramountâsuch as financial transactions, sequential state changes, or multi-step workflowsâavoid relying on parallel dispatching. Instead, embrace job chaining using the `then()` method. This approach transforms independent tasks into a cohesive sequence, ensuring reliability and predictability in your background processing. By mastering these patterns, you move from simply queuing tasks to orchestrating complex, reliable business processes within your Laravel application.