Attachments with Async Messenger Emails

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our registration email is being sent asynchronously via Messenger. And actually, every email our app sends will now be async. Let's double-check that the weekly report emails are still working.

Hit Ctrl+C to stop the worker process and, just to make sure our database if full of fresh data, reload the fixtures:

php bin/console doctrine:fixtures:load

Now run:

php bin/console app:author-weekly-report:send

Problems with Binary Attachments

Ah! Explosion! Incorrect string value? Wow. Okay. What we're seeing is a real-world limitation of the doctrine transport: it can't handle binary data. This may change in Symfony 4.4 - there's a pull request for it - but it may not be merged in time.

Why does our email contain binary data? Remember: the method that creates the author weekly report email also generates a PDF and attaches it. That PDF is binary... so when Messenger tries to put it into a column that doesn't support binary data... boom! Weird explosion.

If this is a problem for you, there are two fixes. First, instead of Doctrine, use another transport - like AMQP. Second, if you need to use doctrine and you do send binary attachments, instead of saying ->attach() you can say ->attachFromPath() and pass this a path on the filesystem to the file. By doing this, the path to the file is what is stored in the queue. The only caveat is that the worker needs to have access to the file at that path.

Messenger and Tests

There's one other thing I want to show with messenger. Run the tests!

php bin/phpunit

Awesome! There are a bunch of deprecation notices, but the tests do pass. However, run that Doctrine query again to see the queue:

php bin/console doctrine:query:sql 'SELECT * FROM messenger_messages'

Uh oh... the email - the one from our functional test to the registration page - was added to the queue! Why is that a problem? Well, it's not a huge problem... but if we run the messenger:consume command...

php bin/console messenger:consume -vv

That would actually send that email! Again, that's not the end of the world... it's just a little odd - the test environment doesn't need to send real emails.

If you've configured your test environment to use a different database than normal, you're good: your test database queue table will fill up with messages, but you'll never run the messenger:consume command from that environment anyways.

Overriding the Transport in the test Environment

But there's also a way to solve this directly in Messenger. In .env, copy MESSENGER_TRANSPORT_DSN and open up .env.test. Paste this but replace doctrine with in-memory. So: in-memory://

6 lines .env.test
... lines 1 - 4
MESSENGER_TRANSPORT_DSN=in-memory://default

This transport... is useless! And I love it. When Messenger sends something to an "in-memory" transport, the message... actually goes nowhere - it's just discarded.

Run the tests again:

php bin/phpunit

And... check the database:

php bin/console doctrine:query:sql 'SELECT * FROM messenger_messages'

No messages! Next, lets finish our grand journey through Mailer by integrating our Email styling with Webpack Encore.

Leave a comment!

This tutorial is built on Symfony 4.3, but will work well with Symfony 4.4 or 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.6.0
        "knplabs/knp-time-bundle": "^1.8", // v1.9.1
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.1,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.1.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.4.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.3.4
        "symfony/console": "^4.0", // v4.3.4
        "symfony/flex": "^1.0", // v1.6.2
        "symfony/form": "^4.0", // v4.3.4
        "symfony/framework-bundle": "^4.0", // v4.3.4
        "symfony/mailer": "4.3.*", // v4.3.4
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.3.4
        "symfony/sendgrid-mailer": "4.3.*", // v4.3.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.3.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "^4.0", // v4.3.4
        "symfony/web-server-bundle": "^4.0", // v4.3.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.6.2
        "symfony/yaml": "^4.0", // v4.3.4
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.2.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "4.3.*", // v4.3.5
        "symfony/debug-bundle": "^3.3|^4.0", // v4.3.4
        "symfony/dotenv": "^4.0", // v4.3.4
        "symfony/maker-bundle": "^1.0", // v1.13.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.3.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.3.4
    }
}