Sending Emails with Mailer
New in version 4.3: The Mailer component was added in Symfony 4.3 and is currently experimental.The previous solution - Swift Mailer - is still valid: Swift Mailer.
Installation
Caution
The Mailer component is experimental in Symfony 4.3: some backwards compatibilitybreaks could occur before 4.4.
Symfony's Mailer & Mime components form a powerful systemfor creating and sending emails - complete with support for multipart messages, Twigintegration, CSS inlining, file attachments and a lot more. Get them installed with:
- $ composer require symfony/mailer
Transport Setup
Emails are delivered via a "transport". And without installing anything else, youcan deliver emails over smtp
by configuring your .env
file:
- # .env
- MAILER_DSN=smtp://user:[email protected]
Warning
If you are migrating from Swiftmailer (and the Swiftmailer bundle), bewarned that the DSN format is different.
Using a 3rd Party Transport
But an easier option is to send emails via a 3rd party provider. Mailer supportsseveral - install whichever you want:
Service | Install with |
---|---|
Amazon SES | composer require symfony/amazon-mailer |
Gmail | composer require symfony/google-mailer |
MailChimp | composer require symfony/mailchimp-mailer |
Mailgun | composer require symfony/mailgun-mailer |
Postmark | composer require symfony/postmark-mailer |
SendGrid | composer require symfony/sendgrid-mailer |
Each library includes a Symfony Flex recipe that will addexample configuration to your .env
file. For example, suppose you want touse SendGrid. First, install it:
- $ composer require symfony/sendgrid-mailer
You'll now have a new line in your .env
file that you can uncomment:
- # .env
- SENDGRID_KEY=
- MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid
The MAILERDSN
isn't a _real SMTP address: it's a simple format that offloadsmost of the configuration work to mailer. The @sendgrid
part of the addressactivates the SendGrid mailer library that you just installed, which knows allabout how to deliver messages to SendGrid.
The only part you need to change is to set SENDGRID_KEY
to your key (in.env
or .env.local
).
Each transport will have different environment variables that the library will useto configure the actual address and authentication for delivery. Some also haveoptions that can be configured with query parameters on end of the MAILER_DSN
-like ?region=
for Amazon SES. Some transports support sending via http
or smtp
- both work the same, but http
is recommended when available.
Tip
Check the DSN formats for all supported providers.
Creating & Sending Messages
To send an email, autowire the mailer usingMailerInterface
(service id mailer
)and create an Email
object:
- // src/Controller/MailerController.php
- namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Mailer\MailerInterface;
- use Symfony\Component\Mime\Email;
- class MailerController extends AbstractController
- {
- /**
- * @Route("/email")
- */
- public function sendEmail(MailerInterface $mailer)
- {
- $email = (new Email())
- ->from('[email protected]')
- ->to('[email protected]')
- //->cc('[email protected]')
- //->bcc('[email protected]')
- //->replyTo('[email protected]')
- //->priority(Email::PRIORITY_HIGH)
- ->subject('Time for Symfony Mailer!')
- ->text('Sending emails is fun again!')
- ->html('<p>See Twig integration for better HTML integration!</p>');
- $mailer->send($email);
- // ...
- }
- }
That's it! The message will be sent via whatever transport you configured.
Email Addresses
All the methods that require email addresses (from()
, to()
, etc.) acceptboth strings or address objects:
- // ...
- use Symfony\Component\Mime\Address;
- use Symfony\Component\Mime\NamedAddress;
- $email = (new Email())
- // email address as a simple string
- ->from('[email protected]')
- // email address as an object
- ->from(new Address('[email protected]'))
- // email address as an object (email clients will display the name
- // instead of the email address)
- ->from(new NamedAddress('[email protected]', 'Fabien'))
- // ...
- ;
Tip
Instead of calling ->from()
every time you create a new email, you cancreate an event subscriber and listen to theMessageEvent::class
event to set the same From
email to all messages.
Multiple addresses are defined with the addXXX()
methods:
- $email = (new Email())
- ->to('[email protected]')
- ->addTo('[email protected]')
- ->addTo('[email protected]')
- // ...
- ;
Alternatively, you can pass multiple addresses to each method:
- $toAddresses = ['[email protected]', new Address('[email protected]')];
- $email = (new Email())
- ->to(...$toAddresses)
- ->cc('[email protected]', '[email protected]')
- // ...
- ;
Message Contents
The text and HTML contents of the email messages can be strings (usually theresult of rendering some template) or PHP resources:
- $email = (new Email())
- // ...
- // simple contents defined as a string
- ->text('Lorem ipsum...')
- ->html('<p>Lorem ipsum...</p>')
- // attach a file stream
- ->text(fopen('/path/to/emails/user_signup.txt', 'r'))
- ->html(fopen('/path/to/emails/user_signup.html', 'r'))
- ;
Tip
You can also use Twig templates to render the HTML and text contents. Readthe Twig: HTML & CSS section later in this article tolearn more.
File Attachments
Use the attachFromPath()
method to attach files that exist on your file system:
- $email = (new Email())
- // ...
- ->attachFromPath('/path/to/documents/terms-of-use.pdf')
- // optionally you can tell email clients to display a custom name for the file
- ->attachFromPath('/path/to/documents/privacy.pdf', 'Privacy Policy')
- // optionally you can provide an explicit MIME type (otherwise it's guessed)
- ->attachFromPath('/path/to/documents/contract.doc', 'Contract', 'application/msword')
- // you can also use an absolute URL if your PHP config allows getting URLs using fopen()
- // (this is not recommended because your application may or may not work depending on PHP config)
- ->attachFromPath('http://example.com/path/to/documents/contract.doc', 'Contract', 'application/msword')
- ;
Alternatively you can use the attach()
method to attach contents from a stream:
- $email = (new Email())
- // ...
- ->attach(fopen('/path/to/documents/contract.doc', 'r'))
- ;
Embedding Images
If you want to display images inside your email, you must embed theminstead of adding them as attachments. When using Twig to render the emailcontents, as explained later in this article,the images are embedded automatically. Otherwise, you need to embed them manually.
First, use the embed()
or embedFromPath()
method to add an image from afile or stream:
- $email = (new Email())
- // ...
- // get the image contents from a PHP resource
- ->embed(fopen('/path/to/images/logo.png', 'r'), 'logo')
- // get the image contents from an existing file
- ->embedFromPath('/path/to/images/signature.gif', 'footer-signature')
- ;
The second optional argument of both methods is the image name ("Content-ID" inthe MIME standard). Its value is an arbitrary string used later to reference theimages inside the HTML contents:
- $email = (new Email())
- // ...
- ->embed(fopen('/path/to/images/logo.png', 'r'), 'logo')
- ->embedFromPath('/path/to/images/signature.gif', 'footer-signature')
- // reference images using the syntax 'cid:' + "image embed name"
- ->html('<img src="cid:logo"> ... <img src="cid:footer-signature"> ...')
- ;
Twig: HTML & CSS
The Mime component integrates with the Twig template engineto provide advanced features such as CSS style inlining and support for HTML/CSSframeworks to create complex HTML email messages. First, make sure Twig is installed:
- $ composer require symfony/twig-bundle
HTML Content
To define the contents of your email with Twig, use theTemplatedEmail
class. This class extendsthe normal Email
class but adds some new methodsfor Twig templates:
- use Symfony\Bridge\Twig\Mime\TemplatedEmail;
- $email = (new TemplatedEmail())
- ->from('[email protected]')
- ->to(new NamedAddress('[email protected]', 'Ryan'))
- ->subject('Thanks for signing up!')
- // path of the Twig template to render
- ->htmlTemplate('emails/signup.html.twig')
- // pass variables (name => value) to the template
- ->context([
- 'expiration_date' => new \DateTime('+7 days'),
- 'username' => 'foo',
- ])
- ;
Then, create the template:
- {# templates/emails/signup.html.twig #}
- <h1>Welcome {{ email.toName }}!</h1>
- <p>
- You signed up as {{ username }} the following email:
- </p>
- <p><code>{{ email.to[0].address }}</code></p>
- <p>
- <a href="#">Click here to activate your account</a>
- (this link is valid until {{ expiration_date|date('F jS') }})
- </p>
The Twig template has access to any of the parameters passed in the context()
method of the TemplatedEmail
class and also to a special variable calledemail
, which is an instance ofWrappedTemplatedEmail
.
Text Content
When the text content of a TemplatedEmail
is not explicitly defined, mailerwill generate it automatically by converting the HTML contents into text. If youhave league/html-to-markdown installed in your application,it uses that to turn HTML into Markdown (so the text email has some visual appeal).Otherwise, it applies the strip_tags
PHP function to the originalHTML contents.
If you want to define the text content yourself, use the text()
methodexplained in the previous sections or the textTemplate()
method provided bythe TemplatedEmail
class:
- + use Symfony\Bridge\Twig\Mime\TemplatedEmail;
- $email = (new TemplatedEmail())
- // ...
- ->htmlTemplate('emails/signup.html.twig')
- + ->textTemplate('emails/signup.txt.twig')
- // ...
- ;
Embedding Images
Instead of dealing with the <img src="cid: …">
syntax explained in theprevious sections, when using Twig to render email contents you can refer toimage files as usual. First, to simplify things, define a Twig namespace calledimages
that points to whatever directory your images are stored in:
- # config/packages/twig.yaml
- twig:
- # ...
- paths:
- # point this wherever your images live
- '%kernel.project_dir%/assets/images': images
Now, use the special email.image()
Twig helper to embed the images insidethe email contents:
- {# '@images/' refers to the Twig namespace defined earlier #}
- <img src="{{ email.image('@images/logo.png') }}" alt="Logo">
- <h1>Welcome {{ email.toName }}!</h1>
- {# ... #}
Inlining CSS Styles
Designing the HTML contents of an email is very different from designing anormal HTML page. For starters, most email clients only support a subset of allCSS features. In addition, popular email clients like Gmail don't supportdefining styles inside <style> … </style>
sections and you must inlineall the CSS styles.
CSS inlining means that every HTML tag must define a style
attribute withall its CSS styles. This can make organizing your CSS a mess. That's why Twigprovides a CssInlinerExtension
that automates everything for you. Installit with:
- $ composer require twig/cssinliner-extension
The extension is enabled automatically. To use this, wrap the entire templatewith the inline_css
filter:
- {% apply inline_css %}
- <style>
- {# here, define your CSS styles as usual #}
- h1 {
- color: #333;
- }
- </style>
- <h1>Welcome {{ email.toName }}!</h1>
- {# ... #}
- {% endapply %}
Using External CSS Files
You can also define CSS styles in external files and pass them asarguments to the filter:
- {% apply inline_css(source('@css/email.css')) %}
- <h1>Welcome {{ username }}!</h1>
- {# ... #}
- {% endapply %}
You can pass unlimited number of arguments to inline_css()
to load multipleCSS files. For this example to work, you also need to define a new Twig namespacecalled css
that points to the directory where email.css
lives:
- # config/packages/twig.yaml
- twig:
- # ...
- paths:
- # point this wherever your css files live
- '%kernel.project_dir%/assets/css': css
Rendering Markdown Content
Twig provides another extension called MarkdownExtension
that lets youdefine the email contents using Markdown syntax. To use this, install theextension and a Markdown conversion library (the extension is compatible withseveral popular libraries):
- # instead of league/commonmark, you can also use erusev/parsedown or michelf/php-markdown
- $ composer require twig/markdown-extension league/commonmark
The extension adds a markdown
filter, which you can use to convert parts orthe entire email contents from Markdown to HTML:
- {% apply markdown %}
- Welcome {{ email.toName }}!
- ===========================
- You signed up to our site using the following email:
- `{{ email.to[0].address }}`
- [Click here to activate your account]({{ url('...') }})
- {% endapply %}
Inky Email Templating Language
Creating beautifully designed emails that work on every email client is socomplex that there are HTML/CSS frameworks dedicated to that. One of the mostpopular frameworks is called Inky. It defines a syntax based on some simpletags which are later transformed into the real HTML code sent to users:
- <!-- a simplified example of the Inky syntax -->
- <container>
- <row>
- <columns>This is a column.</columns>
- </row>
- </container>
Twig provides integration with Inky via the InkyExtension
. First, installthe extension in your application:
- $ composer require twig/inky-extension
The extension adds an inky
filter, which can be used to convert parts or theentire email contents from Inky to HTML:
- {% apply inky %}
- <container>
- <row class="header">
- <columns>
- <spacer size="16"></spacer>
- <h1 class="text-center">Welcome {{ email.toName }}!</h1>
- </columns>
- {# ... #}
- </row>
- </container>
- {% endapply %}
You can combine all filters to create complex email messages:
- {% apply inky|inline_css(source('@css/foundation-emails.css')) %}
- {# ... #}
- {% endapply %}
This makes use of the css Twig namespace we createdearlier. You could, for example, download the foundation-emails.css filedirectly from GitHub and save it in assets/css
.
Sending Messages Async
When you call $mailer->send($email)
, the email is sent to the transport immediately.To improve performance, you can leverage Messenger to sendthe messages later via a Messenger transport.
Start by following the Messenger documentation and configuringa transport. Once everything is set up, when you call $mailer->send()
, aSendEmailMessage
message willbe dispatched through the default message bus (messenger.default_bus
). Assumingyou have a transport called async
, you can route the message there:
- YAML
- # config/packages/messenger.yaml
- framework:
- messenger:
- transports:
- async: "%env(MESSENGER_TRANSPORT_DSN)%"
- routing:
- 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async
- XML
- <!-- config/packages/messenger.xml -->
- <?xml version="1.0" encoding="UTF-8" ?>
- <container xmlns="http://symfony.com/schema/dic/services"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:framework="http://symfony.com/schema/dic/symfony"
- xsi:schemaLocation="http://symfony.com/schema/dic/services
- https://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/symfony
- https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
- <framework:config>
- <framework:messenger>
- <framework:routing message-class="Symfony\Component\Mailer\Messenger\SendEmailMessage">
- <framework:sender service="async"/>
- </framework:routing>
- </framework:messenger>
- </framework:config>
- </container>
- PHP
- // config/packages/messenger.php
- $container->loadFromExtension('framework', [
- 'messenger' => [
- 'routing' => [
- 'Symfony\Component\Mailer\Messenger\SendEmailMessage' => 'async',
- ],
- ],
- ]);
Thanks to this, instead of being delivered immediately, messages will be sent tothe transport to be handled later (see Consuming Messages (Running the Worker)).
Development & Debugging
Disabling Delivery
While developing (or testing), you may want to disable delivery of messages entirely.You can do this by forcing Mailer to use the NullTransport
in only the dev
environment:
- # config/packages/dev/mailer.yaml
- framework:
- mailer:
- dsn: 'smtp://null'
Note
If you're using Messenger and routing to a transport, the message will _still_be sent to that transport.
Always Send to the Same Address
Instead of disabling delivery entirely, you might want to always send emails toa specific address, instead of the real address. To do that, you can takeadvantage of the EnvelopeListener
and register it only for the dev
environment:
- # config/services_dev.yaml
- services:
- mailer.dev.set_recipients:
- class: Symfony\Component\Mailer\EventListener\EnvelopeListener
- tags: ['kernel.event_subscriber']
- arguments:
- $sender: null
- $recipients: ['[email protected]']