Image created with Canva

How Do Queues Boost Email Efficiency in PHP Symfony

Serghei Pogor
5 min readApr 7, 2024

--

Imagine you’re running a big sale on your website. You want to tell everyone about it, right?

So, you decide to send an email to all your users. If you try to send all these emails at once, your website might become as slow as a snail race because it’s too busy sending emails instead of loading pages for your visitors.

😴 Not cool, right?

This is where queues come into play. Think of a queue like a line at a bakery. Instead of everyone rushing to get their bread at once, people line up and get served one by one.

This way, the bakery runs smoothly, and everyone gets their bread without chaos. 🍞

In Symfony, we can use queues to line up emails and send them one by one, in the background. This means your website can keep zooming fast, loading pages for users, while the emails are being sent out quietly in the background.

Like magic! ✨

Let’s get our hands dirty with some code, shall we? Here’s a simple example to set up a queue in Symfony for sending emails:

First, you need to install Messenger component if it’s not already there:

composer require symfony/messenger

Next, let’s configure our messenger for async operations in config/packages/messenger.yaml:

framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\YourEmailMessage': async

Here, YourEmailMessage is a custom message class we'll create for our emails. And async is our queue that holds the emails until they're ready to be sent.

Now, let’s create our email message class in src/Message/YourEmailMessage.php:

namespace App\Message;

class YourEmailMessage
{
private $emailContent;

public function __construct(string $emailContent)
{
$this->emailContent = $emailContent;
}

public function getEmailContent(): string
{
return $this->emailContent;
}
}

This class is super simple. It just holds the content of the email we want to send.

Next up, how do we handle this message?

We need a message handler in src/MessageHandler/YourEmailMessageHandler.php:

namespace App\MessageHandler;

use App\Message\YourEmailMessage;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

class YourEmailMessageHandler implements MessageHandlerInterface
{
private $mailer;

public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}

public function __invoke(YourEmailMessage $message)
{
// Here, you'd use $this->mailer to send the email
// For simplicity, let's just echo the email content
echo "Sending email: " . $message->getEmailContent();
}
}

In a real app, you’d use $this->mailer to send the actual email. For now, we're keeping it simple by just printing out the email content.

How do you actually use this in your controller?

Like this:

use App\Message\YourEmailMessage;
use Symfony\Component\Messenger\MessageBusInterface;

public function sendEmail(MessageBusInterface $bus)
{
$emailContent = "Big Sale! Everything must go!";
$bus->dispatch(new YourEmailMessage($emailContent));

// Return a response or something here
}

And voilà!

You’ve just queued an email for sending in the background. Your website stays speedy, and your emails get sent out without slowing down your site.

But What happens if something goes wrong?

What if an email can’t be sent? Great questions!

In the real world, not everything goes as planned. Sometimes, emails bounce back, or the server might hiccup.

This is where the beauty of using queues really shines. Instead of crashing your user’s experience, the queued job can simply be retried later. It’s like getting a second chance to make a first impression. 💌

Let’s expand our example to handle failures gracefully. In Symfony, you can configure retries. Here’s how you tweak your messenger.yaml to make your app more resilient:

framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 3
delay: 1000
multiplier: 2
routing:
'App\Message\YourEmailMessage': async

This configuration means if sending an email fails, Symfony will wait 1 second (1000 milliseconds), then try again, up to 3 times.

The delay doubles each time, so it waits 1 second, then 2 seconds, then 4 seconds. It’s like the app is saying, “Let’s try that again, but give it a bit more space.” 🚀

But what about monitoring and managing these queued emails?

Symfony got you covered! Enter the Symfony Messenger’s failed transport. This acts like a safety net, catching any messages that couldn’t be delivered after all retries. To set this up, add the following to your messenger.yaml:

framework:
messenger:
failure_transport: failed
transports:
# Other transport configuration...
failed: 'doctrine://default?queue_name=failed'
# This sets up a special transport for failed messages

Now, if a message completely fails to send after all retries, it won’t just disappear. It gets moved to the failed queue.

You can then view these failed messages, find out why they failed, and even retry them manually once the issue is fixed. This is done via the Symfony console command:

php bin/console messenger:failed:show
php bin/console messenger:failed:retry

These commands are like having a conversation with your application. “Hey, what messages couldn’t make it? Let’s give them another shot.” 🤓

Incorporating queues for your emails not only makes your application more efficient but also smarter and more reliable. It’s like teaching your app to be patient and persistent, qualities that define great developers as well!

Keep an Eye on Queue Size 🧐

First off, keeping your queue size in check is crucial. A massive queue might mean your emails are sending slower than expected. Here’s a simple yet effective tip: monitor your queue size regularly. If it starts ballooning, consider scaling up your worker processes or optimizing your email-sending logic.

Optimize Your Workers 🏃‍♂️💨

Speaking of workers, optimizing them can make a big difference. For example, ensure your worker processes are set up to restart after processing a certain number of messages. This helps prevent memory leaks and keeps things running smoothly.

Here’s a snippet on how you might set this up in a Symfony command:

# Restart the worker every 100 messages to keep it fresh
php bin/console messenger:consume async --limit=100

Use Priority Queues for Important Emails 🌟

Not all emails are created equal. Some are more important and need to be sent out ASAP. Symfony allows you to set up priority queues, ensuring critical emails, like password resets, jump to the front of the line.

framework:
messenger:
transports:
async_priority_high:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
queue_name: 'high_priority'
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'

And route your messages accordingly in your messenger.yaml:

framework:
messenger:
routing:
'App\Message\YourImportantEmailMessage': async_priority_high

Keep It Simple with Logging 📝

Lastly, don’t forget about logging! Simple logs can give you insights into what’s happening with your email queues without overcomplicating things. Log successful sends, failures, and retries. This information is invaluable for debugging and optimizing.

$this->logger->info('Email queued', ['email' => $emailDetails]);

Final Thoughts

Remember, the goal isn’t just to send emails.

It’s to create a smooth and responsive application that keeps your users happy and engaged. Email queues are a tool in your toolbox, not the end goal.

Always keep the big picture in mind.

🔔 Click Subscribe to catch more coding fun.
👏🏻 Love it? Give a big clap.
💬 Got a cool idea or funny coding joke? Drop it in the comments.

Share these tips with your fellow developers to help each other succeed together.

Thanks for hanging out and reading. You rock! 🚀

Hold on a sec!!! Want more of my fun stuff in your inbox? Sign up here! 📩

--

--