Updated June 2026. Tested on Symfony 7.x and PHP 8.4. Part of the Techalyst Symfony series.

Some work has no business holding up a web response: sending an email, resizing an image, calling a slow third-party API, generating a report. If you do it inside the request, the user waits. Symfony Messenger lets you hand that work off to be processed elsewhere, so the response returns immediately and the slow part happens in the background. The same code can run synchronously in development and asynchronously in production, which makes it easy to adopt.

Messages and handlers

The pattern has two pieces. A message is a plain object that describes something to do, just data. A handler is the class that does it, marked with #[AsMessageHandler]:

// the message: a simple DTO
class SendWelcomeEmail
{
    public function __construct(public readonly int $userId) {}
}
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
class SendWelcomeEmailHandler
{
    public function __construct(private MailerInterface $mailer) {}

    public function __invoke(SendWelcomeEmail $message): void
    {
        // load the user, send the email
    }
}

Messenger matches the message to its handler by type, the handler whose argument is SendWelcomeEmail gets called when that message is dispatched.

Dispatching

You send a message to the bus, and that is all the dispatching code does. It does not know or care whether the work runs now or later:

public function register(MessageBusInterface $bus): Response
{
    // ...create the user
    $bus->dispatch(new SendWelcomeEmail($user->getId()));

    return $this->redirectToRoute('home'); // returns immediately
}

Sync until you add a transport

Here is the part that makes Messenger easy to start with. Out of the box, with no transport configured, dispatching runs the handler immediately, synchronously, in the same request. That is fine for development. To make it asynchronous, you configure a transport, a queue backed by Doctrine, Redis, or a message broker, and route the message to it:

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            App\Message\SendWelcomeEmail: async

Now dispatching the message drops it on the async queue instead of running it inline, and the response returns without waiting.

The worker

Something has to pull messages off the queue and run their handlers. That is a worker, a long-running process you start with a console command:

php bin/console messenger:consume async

In production you keep this running under a process manager like systemd or supervisor, often several of them for throughput. The worker picks up each queued message, runs its handler, and moves on. Messenger also handles retries on failure and can route messages that keep failing to a separate failure transport so they do not block the queue.

Wrapping up

Symfony Messenger moves slow work out of the request. You describe the work as a message object and write a handler marked with #[AsMessageHandler], then dispatch the message to the bus. With no transport it runs synchronously, which is handy for development, and configuring an async transport plus routing turns the same code asynchronous, with a messenger:consume worker processing the queue. It is the clean way to keep responses fast while emails, image processing, and other heavy jobs happen in the background.