Setting "From" Globally

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

I don't like to have this ->from() on every single email that I create. This will probably always be the same, so let's set it globally.

We know that Mailer dispatches an event each time it sends an email. So, we could probably create a listener for that event and set the from address from there!

But wait. A minute ago, we configured EnvelopeListener as a service in the dev environment and used it to globally override the recipients. This class also allows us to pass a "sender" as the first argument. If we did, it would override the sender on this "envelope" thing.

So, is setting the from globally as easy as passing a value to the first argument of EnvelopeListener? Is this video about 10 seconds from being over?

From Versus Sender

Sadly... no. Remember when I mentioned that an email is two parts: a message and then an envelope around that message? When you set the ->to() on an Email, that goes into the message. The recipients is what goes on the envelope... which totally impacts where the email is delivered, but does not impact who the email appears to be addressed to when reading the email.

The same is true when it comes to from() versus "sender". But this... is even more subtle. The "sender" is the address that's written on the envelope and the from is what actually goes into the message - this is the part that the user will see when reading the email. It's a weird distinction: it's like if someone mailed a letter on your behalf: they would be the sender - with their address on the envelope. But when you opened the envelope, the message inside would be signed from you.

The point is, setting the "sender" is not enough. When we set the from(), Mailer does automatically use that to set the "sender" on the envelope... unless it was set explicitly. But it does not do it the other way around: if we removed the ->from() line and only set the sender, Mailer would give us a huge error because the message would have no from.

So what does this all mean? It means EnvelopeListener can't help us: we need to override the "from", not the "sender". No problem: let's create our own event listener.

Creating the Event Subscriber

In the src/ directory, create a new directory called EventListener. And inside, a new PHP class called SetFromListener. Make this implement EventSubscriberInterface: the interface for all subscribers. I'll go to the "Code -> Generate" menu - or Command + N on a Mac - and hit "Implement Methods" to add the one method required by this interface: getSubscribedEvents().

... lines 1 - 2
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
... lines 6 - 9
class SetFromListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
... lines 14 - 16
}
... lines 18 - 27
}

Inside, return an array: we want to listen to MessageEvent. So: MessageEvent::class => 'onMessage'. When this event occurs, call the onMessage method... which we need to create!

... lines 1 - 2
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\Event\MessageEvent;
... lines 7 - 9
class SetFromListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
MessageEvent::class => 'onMessage',
];
}
... lines 18 - 27
}

On top, add public function onMessage(). Because we're listening to MessageEvent, that will be the first argument: MessageEvent $event.

... lines 1 - 9
class SetFromListener implements EventSubscriberInterface
{
... lines 12 - 18
public function onMessage(MessageEvent $event)
{
... lines 21 - 26
}
}

So... what's inside of this event object anyways? Surprise! The original Email! Ok, maybe that's not too surprising. Add $email = $event->getMessage().

... lines 1 - 9
class SetFromListener implements EventSubscriberInterface
{
... lines 12 - 18
public function onMessage(MessageEvent $event)
{
$email = $event->getMessage();
... lines 22 - 26
}
}

But... is that... truly our original Email object... or is it something else? Hold Command or Ctrl and click the getMessage() method to jump inside. Hmm, this returns something called a RawMessage. What's that?

We have been working with Email objects or TemplatedEmail objects. Open up TemplatedEmail and... let's dig! TemplatedEmail extends Email... Email extends Message... and Message extends... ah ha! RawMessage!

Oooook. We typically work with TemplatedEmail or Email, but on a really, really low level, all Mailer really needs is an instance of RawMessage. Let's... close a few files. The point is: when we call $event->getMessage(), this will return whatever object was actually passed to the send() method... which in our case is always going to be a TemplatedEmail object. But just to be safe, let's add if !$email instanceof Email - make sure you get the one from the Mime component - just return. This shouldn't happen... but could in theory if a third-party bundle sends emails. If you want to be safe, you could also throw an exception here so you know if this happens.

... lines 1 - 6
use Symfony\Component\Mime\Email;
... lines 8 - 9
class SetFromListener implements EventSubscriberInterface
{
... lines 12 - 18
public function onMessage(MessageEvent $event)
{
$email = $event->getMessage();
if (!$email instanceof Email) {
return;
}
... lines 25 - 26
}
}

Anyways, now that we're sure this is an Email object, we can say $email->from()... go steal the from() inside Mailer... and paste here. Re-type the "S" on NamedAddress and hit tab to add its use statement on top.

... lines 1 - 6
use Symfony\Component\Mime\Email;
use Symfony\Component\Mime\NamedAddress;
... line 9
class SetFromListener implements EventSubscriberInterface
{
... lines 12 - 18
public function onMessage(MessageEvent $event)
{
$email = $event->getMessage();
if (!$email instanceof Email) {
return;
}
$email->from(new NamedAddress('alienmailcarrier@example.com', 'The Space Bar'));
}
}

Tip

In Symfony 4.4 and higher, use new Address() - it works the same way as the old NamedAddress.

That's it! We just globally set the from! Back in Mailer, delete it from sendWelcomeMessage()... and also from the weekly report email.

Testing time! Register with any email - because we know that all emails are being delivered to ryan@symfonycasts.com in the development environment - any password, hit register and... run over to the inbox!

There it is! Welcome to The Space Bar from alienmailer@example.com.

Next, sending an email requires a network call... so it's a heavy operation. We can speed up the user experience by sending emails asynchronously via Messenger.

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
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "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.9", // v1.9.10
        "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
    }
}