HTML Emails with Twig
Every email can contain content in two formats, or "parts": a "text" part and an HTML part. And an email can contain just the text part, just the HTML part or both. Of course, these days, most email clients support HTML, so that's the format you really need to focus on. But there are still some situations where having a text version is useful - so we won't completely forget about text. You'll see what I mean.
The email we just sent did not contain the HTML "part" - only the text version. How do we also include an HTML version of the content? Back in the controller, you can almost guess how: copy the ->text(...)
line, delete the semicolon, paste and change the method to html()
. It's that simple! To make it fancier, put an <h1>
around this.
// ... lines 1 - 17 | |
class SecurityController extends AbstractController | |
{ | |
// ... lines 20 - 47 | |
public function register(MailerInterface $mailer, Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator) | |
{ | |
// ... lines 50 - 52 | |
if ($form->isSubmitted() && $form->isValid()) { | |
// ... lines 54 - 73 | |
$email = (new Email()) | |
// ... lines 75 - 77 | |
->text("Nice to meet you {$user->getFirstName()}! ❤️") | |
->html("<h1>Nice to meet you {$user->getFirstName()}! ❤️</h1>"); | |
// ... lines 80 - 88 | |
} | |
// ... lines 90 - 93 | |
} | |
} |
This email now has two "parts": a text part and an HTML part. The user's email client will choose which to show, usually HTML. Let's see what this looks like in Mailtrap. Click back to get to the registration form again, change the email address, add a password and... register! No errors! Check out Mailtrap.
Yeah! This time we have an HTML version! One of the things I love about Mailtrap is how easily we can see the original HTML source, the text or the rendered HTML.
MIME: The "Multipart" Behind Emails
Or, you can check what the "Raw" message looks like. Ooooo, nerdy. It turns out that what an email looks like under-the-hood is almost exactly what an HTTP response looks like that's returned from our app: it has some headers on top, like To
, From
and Subject
, and content below. But, the content is a bit different. Normally, our app returns an HTTP response whose content is probably HTML or JSON. But this email's content contains two formats all at once: HTML and text.
Check out the Content-Type
header: it's multipart/alternative
and then has this weird boundary
string - _=_symfony
- then some random numbers and letters. Below, we can see the content: the plain-text version of the email on top and the text/html
version below that. That weird boundary
string is placed between these two... and literally acts as a separator: it's how the email client knows where the "text" content stops and the next "part" of the message - the HTML part - begins. Isn't that cool? I mean, if this isn't a hot topic for your next dinner party, I don't know what is.
This is what the Symfony's Mime component helps us build. I mean, sheesh, this is ugly. But all we had to do was use the text()
method to add text content and the html()
method to add HTML content.
Using Twig
So... as simple as this Email was to build, we're not really going to put HTML right inside of our controller. We have our standards! Normally, when we need to write some HTML, we put that in a Twig template. When you need HTML for an email, we'll do the exact same thing. Mailer's integration with Twig is awesome.
First, if you downloaded the course code, you should have a tutorial/
directory with a welcome.html.twig
template file inside. Open up the templates/
directory. To organize our email-related templates, let's create a new sub-directory called email/
. Then, paste the welcome.html.twig
template inside.
<html lang="en"> | |
<head> | |
// ... lines 4 - 54 | |
</head> | |
<body> | |
<div class="body"> | |
<div class="container"> | |
<div class="header text-center"> | |
<a href="#homepage"> | |
<img src="path/to/logo.png" class="logo" alt="SpaceBar Logo"> | |
</a> | |
</div> | |
<div class="content"> | |
<h1 class="text-center">Nice to meet you %name%!</h1> | |
<p class="block"> | |
Welcome to <strong>the Space Bar</strong>, we can't wait to read what you have to write. | |
Get started on your first article and connect with the space bar community. | |
</p> | |
// ... lines 70 - 83 | |
</div> | |
// ... lines 85 - 93 | |
</div> | |
</div> | |
</body> | |
</html> |
Say hello to our fancy new templates/email/welcome.html.twig
file. This is a full HTML page with embedded styling via a <style>
tag... and... nothing else interesting: it's 100% static. This %name%
thing I added here isn't a variable: it's just a reminder of something that we need to make dynamic later.
But first, let's use this! As soon as your email needs to leverage a Twig template, you need to change from the Email
class to TemplatedEmail
.
Hold Command or Ctrl and click that class to jump into it. Ah, this TemplatedEmail
class extends the normal Email
: we're really still using the same class as before, but with a few extra methods related to templates. Let's use one of these. Remove both the html()
and text()
calls - you'll see why in a minute - and replace them with ->htmlTemplate()
and then the normal path to the template: email/welcome.html.twig
.
// ... lines 1 - 8 | |
use Symfony\Bridge\Twig\Mime\TemplatedEmail; | |
// ... lines 10 - 18 | |
class SecurityController extends AbstractController | |
{ | |
// ... lines 21 - 48 | |
public function register(MailerInterface $mailer, Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator) | |
{ | |
// ... lines 51 - 53 | |
if ($form->isSubmitted() && $form->isValid()) { | |
// ... lines 55 - 74 | |
$email = (new TemplatedEmail()) | |
// ... lines 76 - 77 | |
->subject('Welcome to the Space Bar!') | |
->htmlTemplate('email/welcome.html.twig'); | |
// ... lines 80 - 88 | |
} | |
// ... lines 90 - 93 | |
} | |
} |
And... that's it! Before we try this, let's make a few things in the template dynamic, like the URLs and the image path. But, there's an important thing to remember with emails: paths must always be absolute. That's next.
For some reason changes in my Twig template don't make it into the mail. Any guesses what might cause this issue? I already deleted the cache manually and with cache:clear but still the same. I'm pretty clueless why the template is not changing.
UPDATE: Ok. With this GitHub discussion I was able to answer the question myself... It is simply not supported in a worker context. So if templated mails are sent via a worker, one has to restart the worker to see the changed templates.