Asynchronous email sending with Symfony
- Nico Hiort af Ornäs
- Tech
- February 25, 2024
Below article serves as a reminder for myself on how to quickly setup asynchronous email sending for Symfony.
We will use Symfony messenger with Doctrine as the queue mechanism.
The below instructions have been tested for Symfony version 6.4.
Install composer packages
Install the required composer packages to do the actual asynchronous sending:
composer require symfony/messenger;
composer require symfony/doctrine-messenger;
And for testing purposes:
composer require --dev zenstruck/messenger-test;
Configure messenger
Add the following YAML configuration file in /config/packages/messenger.yaml
.
framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 10
delay: 10000 # milliseconds delay
# causes the delay to be higher before each retry
# e.g. 10 seconds delay, 20 seconds, 40 seconds
multiplier: 2
max_delay: 0
failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
# Route your messages to the transports
# 'App\Message\YourMessage': async
'Symfony\Component\Mailer\Messenger\SendEmailMessage': async
when@test:
framework:
messenger:
transports:
# replace with your transport name here (e.g., my_transport: 'in-memory://')
# For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
async: 'test://'
when@dev:
framework:
messenger:
transports:
# replace with your transport name here (e.g., my_transport: 'in-memory://')
# For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
async: 'sync://'
As you noticed, in test environment, we will be using the zenstruck/messenger-test library, and in dev environment we will send email still synchronously.
You may have also noticed that it uses an ENV variable MESSENGER_TRANSPORT_DSN
. Please add a definition for it in your .env file:
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=1
Install supervisor
Install supervisor package, e.g.:
sudo apt-get install supervisor;
Add the below configuration in /etc/supervisor/conf.d/messenger-worker.conf
.
[program:messenger-consume]
command=php /path/to/symfony/bin/console messenger:consume async --limit=10 -v
user=www-data
numprocs=1
startsecs=0
autostart=true
autorestart=true
startretries=10
process_name=%(program_name)s_%(process_num)02d
Update your tests
The zenstruck/messenger-test
library provides many useful methods to assert email sending.
Ensure that your test class implements the following trait:
use Zenstruck\Messenger\Test\InteractsWithMessenger;
use Symfony\Component\Mailer\Messenger\SendEmailMessage;
// Ensure one email is sent
$this->transport()->queue()->assertNotEmpty();
$this->transport()->queue()->assertCount(1);
$this->transport()->queue()->assertContains(SendEmailMessage::class);
// Ensure email contains $string
$messages = $this->transport()->queue()->messages(SendEmailMessage::class);
/** @var Email $email */
$email = $messages[0]->getMessage();
/** @var TextPart $textPart */
$textPart = $email->getBody();
$emailBody = $textPart->getBody();
$this->assertStringContainsString($string, $emailBody);