If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeI 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?
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.
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.
Hey Lydie,
Sure, it IS! You can make up a new env var, e.g. MAILER_FROM and thanks to Symfony env system, you can set different values for different environments. Then, you would need the same subscriber as we do, but also inject the actual MAILER_FROM value into it.
Cheers!
Thx victor
I have created 2 new env variables: MAILER_FROM and MAILER_FROM_LABEL. Then I configure them in parameters section in services.yaml file and bind them to allow me to use them in a the SetFromListener class.
// 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.17.6
"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
}
}
Hello!
Is it possible to configure the From globally but different per environment?
Thx!