Email from CLI Command
Lucky you! You found an early release chapter - it will be fully polished and published shortly!
This Chapter isn't quite ready...
Rest assured, the gnomes are hard at work
completing this video!
We've done the prep work for our reminder email feature. Now, let's actually create and send the emails!
Reminder Email Template
In templates/email
, the new email template will be super similar to
booking_confirmation.html.twig
. Copy that file and name it booking_reminder.html.twig
.
Inside, I don't want to spend too much time on this, so just change the
accent title to say "Coming soon!":
{% extends 'email/layout.html.twig' %} | |
{% block content %} | |
<row> | |
<columns> | |
<spacer size="40"></spacer> | |
<p class="accent-title">Coming soon!</p> | |
<h1 class="trip-name">{{ trip.name }}</h1> | |
<img | |
class="trip-image float-center" | |
src="{{ email.image('@images/%s.png'|format(trip.slug)) }}" | |
alt="{{ trip.name }}"> | |
</columns> | |
</row> | |
<row> | |
<columns> | |
<p class="accent-title">Departure: {{ booking.date|date('Y-m-d') }}</p> | |
</columns> | |
</row> | |
<row> | |
<columns> | |
<button class="expanded rounded center" href="{{ url('booking_show', {uid: booking.uid}) }}"> | |
Manage Booking | |
</button> | |
<button class="expanded rounded center secondary" href="{{ url('bookings', {uid: customer.uid}) }}"> | |
My Account | |
</button> | |
</columns> | |
</row> | |
{% endblock %} |
Ship it! Accidental space pun!
Send Reminder Command
The logic to send the emails needs to be something we can schedule to run every hour or every day. Perfect job for a CLI command! At your terminal, run:
symfony make:command
Bah!
symfony console make:command
Call it: app:send-booking-reminders
.
Go check it out! src/Command/SendBookingRemindersCommand.php
. Change the description to
"Send booking reminder emails":
// ... lines 1 - 17 | |
( | |
// ... line 19 | |
description: 'Send booking reminder emails', | |
) | |
class SendBookingRemindersCommand extends Command | |
// ... lines 23 - 70 |
In the constructor, autowire & set properties for BookingRepository
, EntityManagerInterface
and MailerInterface
:
// ... lines 1 - 21 | |
class SendBookingRemindersCommand extends Command | |
{ | |
public function __construct( | |
private BookingRepository $bookingRepo, | |
private EntityManagerInterface $em, | |
private MailerInterface $mailer, | |
) { | |
parent::__construct(); | |
} | |
// ... lines 31 - 68 | |
} |
This command doesn't need any arguments or options, so remove the configure()
method entirely.
Clear out the guts of execute()
. Start by adding a nice:
$io->title('Sending booking reminders')
. Then, grab the bookings that need
reminders sent, with $bookings = $this->bookingRepo->findBookingsToRemind()
.
Easy Progress Bar
To be the absolute best, let's show a progress bar as we loop over the bookings.
The $io
object has a trick for this.
Write foreach ($io->progressIterate($bookings) as $booking)
. This handles
all the boring progress bar logic for us! Inside, we need to create a new
email. In TripController
, copy that email - including these headers, and
paste it here.
But we need to adjust this a bit: remove the attachment. And for the subject: replace
"Confirmation" with "Reminder". Above, add some variables for convenience:
$customer = $booking->getCustomer()
and $trip = $booking->getTrip()
. Down here,
keep the same metadata, but change the tag to booking_reminder
. This will
help us better distinguish these emails in Mailtrap.
Oh, and of course, change the template to booking_reminder.html.twig
.
Still in the loop, send the email with $this->mailer->send($email)
and mark
the booking as having the reminder sent with
$booking->setReminderSentAt(new \DateTimeImmutable('now'))
.
Perfect! Outside the loop, call $this->em->flush()
to save the changes to the database.
Finally, celebrate with
$io->success(sprintf('Sent %d booking reminders', count($bookings)))
.
// ... lines 1 - 21 | |
class SendBookingRemindersCommand extends Command | |
{ | |
// ... lines 24 - 31 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$io->title('Sending booking reminders'); | |
$bookings = $this->bookingRepo->findBookingsToRemind(); | |
foreach ($io->progressIterate($bookings) as $booking) { | |
$trip = $booking->getTrip(); | |
$customer = $booking->getCustomer(); | |
$email = (new TemplatedEmail()) | |
->to(new Address($customer->getEmail())) | |
->subject('Booking Reminder for '.$trip->getName()) | |
->htmlTemplate('email/booking_reminder.html.twig') | |
->context([ | |
'customer' => $customer, | |
'trip' => $trip, | |
'booking' => $booking, | |
]) | |
; | |
$email->getHeaders()->add(new TagHeader('booking_reminder')); | |
$email->getHeaders()->add(new MetadataHeader('booking_uid', $booking->getUid())); | |
$email->getHeaders()->add(new MetadataHeader('customer_uid', $customer->getUid())); | |
$this->mailer->send($email); | |
$booking->setReminderSentAt(new \DateTimeImmutable('now')); | |
} | |
$this->em->flush(); | |
$io->success(sprintf('Sent %d booking reminders', count($bookings))); | |
return Command::SUCCESS; | |
} | |
} |
Testing time! Pop over to your terminal. To be sure we have a booking that needs a reminder sent, reload the fixtures with:
symfony console doctrine:fixture:load
Now, run our new command!
symfony console app:send-booking-reminders
Nice, 1 reminder sent! And the output will impress our colleagues! Before we check Mailtrap, run the command again:
symfony console app:send-booking-reminders
"Sent 0 booking reminders". Perfect! Our logic to mark bookings as having reminders sent works!
Now check Mailtrap... here it is! As expected, it looks super similar to our confirmation email but, it says "Coming soon!" here: it's using the new template.
X-Tag
and X-Metadata
When using "Mailtrap Testing", Mailer tags and metadata are not converted to Mailtrap
categories and custom variables like they are when sent in production. But you can still
make sure they're being sent! Click this "Tech Info" tab and scroll down a bit.
When Mailer doesn't know how to convert tags and metadata, it adds them as these generic
custom headers: X-Tag
and X-Metadata
.
Sure enough, X-Tag
is booking_reminder
. Awesome, that's what we expect too!
Ok, new feature? Check! Test for the new feature? That's next!