This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
09.

Global From (and Fun) with Email Events

|

Share this awesome video!

|

I bet that most, if not every email your app sends will be from the same email address, something clever like hal9000@universal-travel.com or the tried-and-true but sleepier info@universal-travel.com.

Because every email will have the same from address, there's no point to set it in every email. Instead, let's set it globally. Oddly, there isn't any tiny config option for this. But that's great for us: it gives us a chance to learn about events! Very powerful, very nerdy.

The MessageEvent

Before an email is sent, Mailer dispatches a MessageEvent.

To listen to this, find your terminal and run:

symfony console make:listener

Call it GlobalFromEmailListener. The gives us a list of events we can listen to. We want the first one: MessageEvent. Start typing Symfony and it's autocompleted for us. Hit enter.

Listener created!

To be extra cool, let's set our global from address as a parameter. In config/services.yaml, under parameters, add a new one: global_from_email.

Special Email Address String

This will be a string, but check this out: set it to Universal Travel , then in angle brackets, put the email: <info@universal-travel.com>:

26 lines | config/services.yaml
// ... lines 1 - 5
parameters:
global_from_email: 'Universal Travel <info@universal-travel.com>'
// ... lines 8 - 26

When Symfony Mailer sees a string that looks like this as an email address, it'll create the proper Address object with both a name and email set. Sweet!

MessageEvent Listener

Open the new class src/EventListener/GlobalFromEmailListener.php. Add a constructor with a private string $fromEmail argument and an #[Autowire] attribute with our parameter name: %global_from_email%:

// ... lines 1 - 8
final class GlobalFromEmailListener
{
public function __construct(
#[Autowire('%global_from_email%')]
private string $fromEmail,
) {
}
// ... lines 16 - 21
}

Down here, the #[AsEventListener] attribute is what marks this method as an event listener. We can actually remove this event argument - it'll be inferred from the method argument's type-hint: MessageEvent:

// ... lines 1 - 9
final class GlobalFromEmailListener
{
// ... lines 12 - 17
#[AsEventListener]
public function onMessageEvent(MessageEvent $event): void
{
// ... lines 21 - 31
}
}

Inside, first grab the message from the event: $message = $event->getMessage():

// ... lines 1 - 9
final class GlobalFromEmailListener
{
// ... lines 12 - 18
public function onMessageEvent(MessageEvent $event): void
{
$message = $event->getMessage();
// ... lines 22 - 31
}
}

Jump into the getMessage() method to see what it returns. RawMessage... jump into this and look at what classes extend it. TemplatedEmail! Perfect!

Back in our listener, write if (!$message instanceof TemplatedEmail), and inside, return;:

// ... lines 1 - 9
final class GlobalFromEmailListener
{
// ... lines 12 - 18
public function onMessageEvent(MessageEvent $event): void
{
// ... lines 21 - 22
if (!$message instanceof TemplatedEmail) {
return;
}
// ... lines 26 - 31
}
}

This will likely never be the case, but it's good practice to double-check. Plus, it helps our IDE know that $message is a TemplatedEmail now.

It's possible that an email might still set its own from address. In this case, we don't want to override it. So, add a guard clause: if ($message->getFrom()), return;:

// ... lines 1 - 9
final class GlobalFromEmailListener
{
// ... lines 12 - 18
public function onMessageEvent(MessageEvent $event): void
{
// ... lines 21 - 26
if ($message->getFrom()) {
return;
}
// ... lines 30 - 31
}
}

Now, we can set the global from: $message->from($this->fromEmail):

// ... lines 1 - 9
final class GlobalFromEmailListener
{
// ... lines 12 - 18
public function onMessageEvent(MessageEvent $event): void
{
// ... lines 21 - 30
$message->from($this->fromEmail);
}
}

Perfect!

Back in TripController::show(), remove the ->from() for the email.

Time to test this! In our app, book a trip and check Mailtrap for the email. Drumroll... the from is set correctly! Our listener works! I never doubted us.

Reply-To

One more detail to make this completely airtight (like most of our ships).

Imagine a contact form where the user fills their name, email, and a message. This fires off an email with these details to your support team. In their email clients, it'd be nice if, when they hit reply, it goes to the email from the form - not your "global from".

You might think that you should set the from address to the user's email. But that won't work, as we're not authorized to send emails on behalf of that user. More on email security soon.

Fortunately, there's a special email header called Reply-To for just this scenario. When building your email, set it with ->replyTo() and pass the user's email address.

Strap in because the booster tanks are full and ready for launch! Time to send real emails in production! That's next.