Create a Job
To dispatch Laravel jobs to RabbitMQ you first need a Job - the unit of work Laravel puts on the queue. Generate one with Artisan:
php artisan make:job SendWelcomeEmail
That creates app/Jobs/SendWelcomeEmail.php. Fill in the constructor with the data the
job needs and put the actual work in handle():
namespace App\Jobs;
use App\Models\User;
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\Mail;
use App\Mail\WelcomeMail;
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public User $user)
{
}
public function handle(): void
{
Mail::to($this->user)->send(new WelcomeMail($this->user));
}
}
Two things matter. implements ShouldQueue is what tells Laravel to queue the job
rather than run it immediately - without it, dispatch runs inline. The SerializesModels
trait lets you pass an Eloquent model to the constructor; Laravel stores only its id in
the message and re-loads a fresh model when the worker runs.
That re-load is exactly where a subtle bug lives. Dispatch a job inside a database
transaction and RabbitMQ can receive the message before the transaction commits - a fast
worker then queries for a row that does not exist yet and the job dies on a "model not
found". The fix is to defer publishing until after commit, either per dispatch with
->afterCommit() or by setting 'after_commit' => true on the connection in
config/queue.php.
Dispatch it
Dispatching puts the job on the queue. The simplest form is the static dispatch method:
use App\Jobs\SendWelcomeEmail;
SendWelcomeEmail::dispatch($user);
Because QUEUE_CONNECTION=rabbitmq from the previous lesson, this publishes a message to
RabbitMQ. Under the hood the driver serializes the job to JSON and does the basic_publish
you did by hand in Chapter 3 - you just call one method. The controller returns right
away; the email is sent later by a worker.
If a queue named in your job (or the default default queue) does not exist yet, the
driver declares it for you on first publish, so you do not pre-create queues in the
management UI.
Send it to a named queue
By default the job lands on the connection's default queue (RABBITMQ_QUEUE, usually
default). You can send it to a different queue with onQueue():
SendWelcomeEmail::dispatch($user)->onQueue('emails');
Now the message goes to a queue called emails instead. This is how you separate kinds of
work - emails on one queue, image processing on another - so they can be consumed by
different workers. We use that fully in
priorities and multiple queues.
Delaying a job
You can ask Laravel to hold a job before it becomes available:
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));
Be aware this needs delay support. The RabbitMQ driver implements delays, but on some setups it relies on a delayed-message mechanism rather than plain AMQP. If delayed jobs seem to run immediately or not at all, that is a configuration matter, not your code - delayed messages were introduced in Chapter 5.
Common mistake
Forgetting implements ShouldQueue. Without it, dispatch() executes the job inline,
in the same request, and nothing ever reaches RabbitMQ - the exact opposite of what you
want. If your "queued" job runs synchronously and slows the request, check that the class
implements ShouldQueue.
FAQ
Where does the job go if I don't call onQueue()?
To the connection's default queue, which is set by RABBITMQ_QUEUE in your .env
(commonly default). onQueue('name') overrides it per dispatch.
Do I have to create the queue in RabbitMQ first?
No. The driver declares the queue on first publish, so dispatching to a new queue name just works. You'll see it appear in the management UI once a message hits it.
Can I pass an Eloquent model to a job?
Yes. With the SerializesModels trait, Laravel stores only the model's identifier in the
message and re-fetches a fresh copy when the worker runs handle(). Pass the model
normally through the constructor.