Decoupling Long‑Running Tasks in Symfony with Messenger
Learn how to offload long‑running tasks like email sending in Symfony using the Messenger component, with a step‑by‑step example and verification tips.
18 Jun 2026, 15:36 UTC

Problem: slow HTTP responses when handling heavy work
When a controller tries to send an email, process an uploaded file, or call an external API directly, the HTTP request waits for that work to finish. Users see longer load times, and the web server may hit time‑out limits under load.
Thesis: Symfony Messenger lets you move that work off the request thread by publishing a message to a transport and processing it with a separate worker.
How Messenger fits into a Symfony app
The component provides a message bus. You define a plain PHP class that carries data (the message), configure a transport (where messages are stored), dispatch the message from your controller, and run a console command that consumes the transport and invokes a handler.
Worked example: sending a welcome email asynchronously
- Create the message class (e.g.,
src/Message/WelcomeEmail.php):<?php namespace App\Message; class WelcomeEmail { public function __construct( public string $email, public string $username ) {} } - Configure a Doctrine transport (good for low‑volume dev): edit
config/packages/messenger.yaml:
Set the environment variable (e.g., inframework: messenger: transports: async: '%env(MESSENGER_TRANSPORT_DSN)%' routing: 'App\Message\WelcomeEmail': async.env):MESSENGER_TRANSPORT_DSN=doctrine://default?queue_name=async - Dispatch the message from a controller (e.g., after user registration):
use App\Message\WelcomeEmail; use Symfony\Component\Messenger\MessageBusInterface; public function register(Request $request, MessageBusInterface $bus): Response { // … persist user … $bus->dispatch(new WelcomeEmail($user->getEmail(), $user->getUsername())); return $this->redirectToRoute('login'); } - Create the handler (e.g.,
src/MessageHandler/WelcomeEmailHandler.php):<?php namespace App\MessageHandler; use App\Message\WelcomeEmail; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; class WelcomeEmailHandler { public function __construct(private MailerInterface $mailer) {} public function __invoke(WelcomeEmail $message): void { $email = (new Email()) ->from('[contact removed]') ->to($message->email) ->subject('Welcome, '.$message->username!) ->text('Thanks for signing up!'); $this->mailer->send($email); } } - Run the consumer (requires CLI access, typically as the same user that runs the web server):
The command blocks, pulling messages from thephp bin/console messenger:consume async -vvmessenger_messagestable and invoking the handler. Check the console output or your mail logs to confirm the email was sent.
Trade‑offs and limitations
- Infrastructure overhead: you must run a worker process (or a supervisor like systemd) and monitor the transport table. In production, Doctrine transport can cause table bloat; consider switching to Redis or RabbitMQ for higher throughput.
- No built‑in deduplication: if the same message is dispatched twice, both will be processed unless you make the handler idempotent or add an external deduplication layer.
- Ordering guarantees: depend on the transport. Doctrine preserves FIFO per queue, but brokers like RabbitMQ may require additional configuration for strict ordering.
Practical verification
- After dispatching a message, check that a row appears in
messenger_messages(Doctrine) or in your broker’s queue. - Run the consumer; observe the handler’s log or side‑effect (e.g., email sent, DB row updated).
- Confirm the Symfony version includes Messenger:
php bin/console aboutshowssymfony/messengerversion ≥ 4.1.
Closing: start small, scale later
Begin with the Doctrine transport to prove the pattern in a local environment. Once the asynchronous flow is reliable, migrate to a dedicated broker and configure a process manager to keep workers running. This approach keeps HTTP responses snappy while giving you a clear path to scale background work.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.