Symfony messenger and mailer : how to add a binding_key?

Viewed 1744

I have a running Symfony 4.4 project with messenger and rabbitMQ. I have an async transport with 2 queues.

    transports:
        # https://symfony.com/doc/current/messenger.html#transport-configuration
      async:
        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        options:
          exchange:
            name: myexchange
            type: direct
          queues:
            email:
              binding_keys:
                - email
            extranet:
              binding_keys:
                  - extranet
        # failed: 'doctrine://default?queue_name=failed'
        # sync: 'sync://'

    routing:
        # Route your messages to the transports
        'App\Message\ExtranetMessage': async
        'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

I need to send email with the symfony/mailer component to the email queue.

public function contact(Request $request, MailerInterface $mailer)
{
    if($request->isXmlHttpRequest())
    {
        //dd($request->request->all());
        $body =
            'Nouveau message depuis le front<br />
             Nom = '.$request->request->get('nom').'<br />
             Prénom = '.$request->request->get('prenom').'<br />
             Société = '.$request->request->get('societe').'<br />
             Email = '.$request->request->get('mail').'<br />';

        $email = (new Email())
            ->from('from@email.tld')
            ->replyTo($request->request->get('mail'))
            ->to('$request->request->get('mail')')
            ->subject('test')
            ->html($body);

        $mailer->send($email);

        return new JsonResponse('OK', 200);
    }
}

How can I add the binding_key to the mailer in order to let rabbitMQ know how to handle the email ?

3 Answers

Routing keys can be specified via stamps. Unfortunately, the mailer integration doesn't expose a way to add them, it just dispatches the message to the default queue. But you can still dispatch the message manually:

$this->dispatchMessage(new SendEmailMessage($email), [new AmqpStamp('email')]);

This approach has some limitations: Since this is not using mailer code, MessageEvent won't be dispatched and the "E-mails" Pane in the profiler will be empty.

Another option is to add the stamp using a middleware:

  1. Create the middleware
// src/Messenger/StampEmailMessageMiddleware.php
class StampEmailMessageMiddleware implements MiddlewareInterface
{
    const bindingKey = 'email';

    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        // Add the stamp. Since the middleware gets called both when dispatching and 
        // consuming the message, we make sure there's no stamp already added.
        if (
            $envelope->getMessage() instanceof SendEmailMessage && 
            null === $envelope->last(AmqpStamp::class)
        ) {
            $envelope = $envelope->with(new AmqpStamp(self::bindingKey));
        }

        return $stack->next()->handle($envelope, $stack);
    }
}

  1. Add the middleware to the bus configuration:
# config/packages/messenger.yaml
messenger:
    buses:
        messenger.bus.default:
            middleware:
                - 'App\Messenger\StampEmailMessageMiddleware'

  1. Send the message normally:
$mailer->send($email);

Allright, I found the answer while searching for the complete messenger configuration reference.

In order to process messages without binding key, a default_publish_routing_key entry has to be added. The configuration now looks like :

    transports:
        # https://symfony.com/doc/current/messenger.html#transport-configuration
      async:
        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        options:
          exchange:
            name: myexchange
            type: direct
            default_publish_routing_key: email
          queues:
            email:
              binding_keys:
                - email
            extranet:
              binding_keys:
                  - extranet
        # failed: 'doctrine://default?queue_name=failed'
        # sync: 'sync://'

    routing:
        # Route your messages to the transports
        'App\Message\ExtranetMessage': async
        'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

This allows the messenger component to process messages event if they don't have any queue specified.

Alternatively you could define separate transports for each queue using different exchange names.

    transports:
      async_email:
        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        options:
          exchange:
            name: messages_email
          queues:
            email: ~
      async_extranet:
        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        options:
          exchange:
            name: messages_extranet
          queues:
            extranet: ~

    routing:
        'App\Message\ExtranetMessage': async_extranet
        'Symfony\Component\Mailer\Messenger\SendEmailMessage': async_email

In this case you don't need to specify binding key on every message dispatch or create custom middleware.

Related