Swift Mailer¶
Note
In Symfony 4.3, the Mailer component was introduced and can be used instead of Swift Mailer.
Symfony provides a mailer feature based on the popular Swift Mailer library via the SwiftMailerBundle. This mailer supports sending messages with your own mail servers as well as using popular email providers like Mandrill, SendGrid, and Amazon SES.
Installation¶
In applications using Symfony Flex, run this command to install the Swift Mailer based mailer before using it:
1 | $ composer require symfony/swiftmailer-bundle
|
If your application doesn’t use Symfony Flex, follow the installation instructions on SwiftMailerBundle.
Configuration¶
The config/packages/swiftmailer.yaml
file that’s created when installing the
mailer provides all the initial config needed to send emails, except your mail
server connection details. Those parameters are defined in the MAILER_URL
environment variable in the .env
file:
1 2 3 4 5 6 7 | # .env (or override MAILER_URL in .env.local to avoid committing your changes)
# use this to disable email delivery
MAILER_URL=null://localhost
# use this to configure a traditional SMTP server
MAILER_URL=smtp://localhost:465?encryption=ssl&auth_mode=login&username=&password=
|
Caution
If the username, password or host contain any character considered special in a
URI (such as +
, @
, $
, #
, /
, :
, *
, !
), you must
encode them. See RFC 3986 for the full list of reserved characters or use the
urlencode
function to encode them.
Refer to the SwiftMailer configuration reference for the detailed explanation of all the available config options.
Sending Emails¶
The Swift Mailer library works by creating, configuring and then sending
Swift_Message
objects. The “mailer” is responsible for the actual delivery
of the message and is accessible via the Swift_Mailer
service. Overall,
sending an email is pretty straightforward:
public function index($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
->setTo('recipient@example.com')
->setBody(
$this->renderView(
// templates/emails/registration.html.twig
'emails/registration.html.twig',
['name' => $name]
),
'text/html'
)
// you can remove the following code if you don't define a text version for your emails
->addPart(
$this->renderView(
// templates/emails/registration.txt.twig
'emails/registration.txt.twig',
['name' => $name]
),
'text/plain'
)
;
$mailer->send($message);
return $this->render(...);
}
To keep things decoupled, the email body has been stored in a template and
rendered with the renderView()
method. The registration.html.twig
template might look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 | {# templates/emails/registration.html.twig #}
<h3>You did it! You registered!</h3>
Hi {{ name }}! You're successfully registered.
{# example, assuming you have a route named "login" #}
To login, go to: <a href="{{ url('login') }}">...</a>.
Thanks!
{# Makes an absolute URL to the /images/logo.png file #}
<img src="{{ absolute_url(asset('images/logo.png')) }}">
|
The $message
object supports many more options, such as including attachments,
adding HTML content, and much more. Refer to the Creating Messages section
of the Swift Mailer documentation for more details.
Using Gmail to Send Emails¶
During development, you might prefer to send emails using Gmail instead of
setting up a regular SMTP server. To do that, update the MAILER_URL
of your
.env
file to this:
1 2 | # username is your full Gmail or Google Apps email address
MAILER_URL=gmail://username:password@localhost
|
The gmail
transport is a shortcut that uses the smtp
transport, ssl
encryption, login
auth mode and smtp.gmail.com
host. If your app uses
other encryption or auth mode, you must override those values
(see mailer config reference):
1 2 | # username is your full Gmail or Google Apps email address
MAILER_URL=gmail://username:password@localhost?encryption=tls&auth_mode=oauth
|
If your Gmail account uses 2-Step-Verification, you must generate an App password and use it as the value of the mailer password. You must also ensure that you allow less secure applications to access your Gmail account.
Using Cloud Services to Send Emails¶
Cloud mailing services are a popular option for companies that don’t want to set
up and maintain their own reliable mail servers. To use these services in a
Symfony app, update the value of MAILER_URL
in the .env
file. For example, for Amazon SES (Simple Email Service):
1 2 3 | # The host will be different depending on your AWS zone
# The username/password credentials are obtained from the Amazon SES console
MAILER_URL=smtp://email-smtp.us-east-1.amazonaws.com:587?encryption=tls&username=YOUR_SES_USERNAME&password=YOUR_SES_PASSWORD
|
Use the same technique for other mail services, as most of the time there is nothing more to it than configuring an SMTP endpoint.
How to Work with Emails during Development¶
When developing an application which sends email, you will often not want to actually send the email to the specified recipient during development. If you are using the SwiftmailerBundle with Symfony, you can achieve this through configuration settings without having to make any changes to your application’s code at all. There are two main choices when it comes to handling email during development: (a) disabling the sending of email altogether or (b) sending all email to a specific address (with optional exceptions).
Disabling Sending¶
You can disable sending email by setting the disable_delivery
option to
true
, which is the default value used by Symfony in the test
environment
(email messages will continue to be sent in the other environments):
- YAML
1 2 3
# config/packages/test/swiftmailer.yaml swiftmailer: disable_delivery: true
- XML
1 2 3 4 5 6 7 8 9 10 11
<!-- config/packages/test/swiftmailer.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:swiftmailer="http://symfony.com/schema/dic/swiftmailer" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> <swiftmailer:config disable-delivery="true"/> </container>
- PHP
1 2 3 4
// config/packages/test/swiftmailer.php $container->loadFromExtension('swiftmailer', [ 'disable_delivery' => "true", ]);
Sending to a Specified Address(es)¶
You can also choose to have all email sent to a specific address or a list of addresses, instead
of the address actually specified when sending the message. This can be done
via the delivery_addresses
option:
- YAML
1 2 3
# config/packages/dev/swiftmailer.yaml swiftmailer: delivery_addresses: ['dev@example.com']
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!-- config/packages/dev/swiftmailer.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:swiftmailer="http://symfony.com/schema/dic/swiftmailer" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> <swiftmailer:config> <swiftmailer:delivery-address>dev@example.com</swiftmailer:delivery-address> </swiftmailer:config> </container>
- PHP
1 2 3 4
// config/packages/dev/swiftmailer.php $container->loadFromExtension('swiftmailer', [ 'delivery_addresses' => ['dev@example.com'], ]);
Now, suppose you’re sending an email to recipient@example.com
in a controller:
public function index($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
->setTo('recipient@example.com')
->setBody(
$this->renderView(
// templates/hello/email.txt.twig
'hello/email.txt.twig',
['name' => $name]
)
)
;
$mailer->send($message);
return $this->render(...);
}
In the dev
environment, the email will instead be sent to dev@example.com
.
Swift Mailer will add an extra header to the email, X-Swift-To
, containing
the replaced address, so you can still see who it would have been sent to.
Note
In addition to the to
addresses, this will also stop the email being
sent to any CC
and BCC
addresses set for it. Swift Mailer will add
additional headers to the email with the overridden addresses in them.
These are X-Swift-Cc
and X-Swift-Bcc
for the CC
and BCC
addresses respectively.
Sending to a Specified Address but with Exceptions¶
Suppose you want to have all email redirected to a specific address,
(like in the above scenario to dev@example.com
). But then you may want
email sent to some specific email addresses to go through after all, and
not be redirected (even if it is in the dev environment). This can be done
by adding the delivery_whitelist
option:
- YAML
1 2 3 4 5 6 7 8
# config/packages/dev/swiftmailer.yaml swiftmailer: delivery_addresses: ['dev@example.com'] delivery_whitelist: # all email addresses matching these regexes will be delivered # like normal, as well as being sent to dev@example.com - '/@specialdomain\.com$/' - '/^admin@mydomain\.com$/'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<!-- config/packages/dev/swiftmailer.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:swiftmailer="http://symfony.com/schema/dic/swiftmailer" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> <swiftmailer:config> <!-- all email addresses matching these regexes will be delivered like normal, as well as being sent to dev@example.com --> <swiftmailer:delivery-whitelist-pattern>/@specialdomain\.com$/</swiftmailer:delivery-whitelist-pattern> <swiftmailer:delivery-whitelist-pattern>/^admin@mydomain\.com$/</swiftmailer:delivery-whitelist-pattern> <swiftmailer:delivery-address>dev@example.com</swiftmailer:delivery-address> </swiftmailer:config> </container>
- PHP
1 2 3 4 5 6 7 8 9 10
// config/packages/dev/swiftmailer.php $container->loadFromExtension('swiftmailer', [ 'delivery_addresses' => ["dev@example.com"], 'delivery_whitelist' => [ // all email addresses matching these regexes will be delivered // like normal, as well as being sent to dev@example.com '/@specialdomain\.com$/', '/^admin@mydomain\.com$/', ], ]);
In the above example all email messages will be redirected to dev@example.com
and messages sent to the admin@mydomain.com
address or to any email address
belonging to the domain specialdomain.com
will also be delivered as normal.
Caution
The delivery_whitelist
option is ignored unless the delivery_addresses
option is defined.
Viewing from the Web Debug Toolbar¶
You can view any email sent during a single response when you are in the
dev
environment using the web debug toolbar. The email icon in the toolbar
will show how many emails were sent. If you click it, a report will open
showing the details of the sent emails.
If you’re sending an email and then immediately redirecting to another page, the web debug toolbar will not display an email icon or a report on the next page.
Instead, you can set the intercept_redirects
option to true
in the
dev
environment, which will cause the redirect to stop and allow you to open
the report with details of the sent emails.
- YAML
1 2 3
# config/packages/dev/web_profiler.yaml web_profiler: intercept_redirects: true
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!-- config/packages/dev/web_profiler.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:webprofiler="http://symfony.com/schema/dic/webprofiler" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/webprofiler https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd"> <webprofiler:config intercept-redirects="true" /> </container>
- PHP
1 2 3 4
// config/packages/dev/web_profiler.php $container->loadFromExtension('web_profiler', [ 'intercept_redirects' => 'true', ]);
Tip
Alternatively, you can open the profiler after the redirect and search
by the submit URL used on the previous request (e.g. /contact/handle
).
The profiler’s search feature allows you to load the profiler information
for any past requests.
Tip
In addition to the features provided by Symfony, there are applications that can help you test emails during application development, like MailCatcher, Mailtrap and MailHog.
How to Spool Emails¶
The default behavior of the Symfony mailer is to send the email messages immediately. You may, however, want to avoid the performance hit of the communication to the email server, which could cause the user to wait for the next page to load while the email is sending. This can be avoided by choosing to “spool” the emails instead of sending them directly.
This makes the mailer to not attempt to send the email message but instead save it somewhere such as a file. Another process can then read from the spool and take care of sending the emails in the spool. Currently only spooling to file or memory is supported.
Spool Using Memory¶
When you use spooling to store the emails to memory, they will get sent right before the kernel terminates. This means the email only gets sent if the whole request got executed without any unhandled exception or any errors. To configure this spool, use the following configuration:
- YAML
1 2 3 4
# config/packages/swiftmailer.yaml swiftmailer: # ... spool: { type: memory }
- XML
1 2 3 4 5 6 7 8 9 10 11 12
<!-- config/packages/swiftmailer.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:swiftmailer="http://symfony.com/schema/dic/swiftmailer" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> <swiftmailer:config> <swiftmailer:spool type="memory"/> </swiftmailer:config> </container>
- PHP
1 2 3 4 5
// config/packages/swiftmailer.php $container->loadFromExtension('swiftmailer', [ // ... 'spool' => ['type' => 'memory'], ]);
Spool Using Files¶
When you use the filesystem for spooling, Symfony creates a folder in the given path for each mail service (e.g. “default” for the default service). This folder will contain files for each email in the spool. So make sure this directory is writable by Symfony (or your webserver/php)!
In order to use the spool with files, use the following configuration:
- YAML
1 2 3 4 5 6
# config/packages/swiftmailer.yaml swiftmailer: # ... spool: type: file path: /path/to/spooldir
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- config/packages/swiftmailer.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:swiftmailer="http://symfony.com/schema/dic/swiftmailer" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd"> <swiftmailer:config> <swiftmailer:spool type="file" path="/path/to/spooldir" /> </swiftmailer:config> </container>
- PHP
1 2 3 4 5 6 7 8 9
// config/packages/swiftmailer.php $container->loadFromExtension('swiftmailer', [ // ... 'spool' => [ 'type' => 'file', 'path' => '/path/to/spooldir', ], ]);
Tip
If you want to store the spool somewhere with your project directory,
remember that you can use the %kernel.project_dir%
parameter to reference
the project’s root:
1 | path: '%kernel.project_dir%/var/spool'
|
Now, when your app sends an email, it will not actually be sent but instead added to the spool. Sending the messages from the spool is done separately. There is a console command to send the messages in the spool:
1 | $ APP_ENV=prod php bin/console swiftmailer:spool:send
|
It has an option to limit the number of messages to be sent:
1 | $ APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=10
|
You can also set the time limit in seconds:
1 | $ APP_ENV=prod php bin/console swiftmailer:spool:send --time-limit=10
|
In practice you will not want to run this manually. Instead, the console command should be triggered by a cron job or scheduled task and run at a regular interval.
Caution
When you create a message with SwiftMailer, it generates a Swift_Message
class. If the swiftmailer
service is lazy loaded, it generates instead a
proxy class named Swift_Message_<someRandomCharacters>
.
If you use the memory spool, this change is transparent and has no impact. But when using the filesystem spool, the message class is serialized in a file with the randomized class name. The problem is that this random class name changes on every cache clear.
So if you send a mail and then you clear the cache, on the next execution of
swiftmailer:spool:send
an error will raise because the class
Swift_Message_<someRandomCharacters>
doesn’t exist (anymore).
The solutions are either to use the memory spool or to load the
swiftmailer
service without the lazy
option (see Lazy Services).
How to Test that an Email is Sent in a Functional Test¶
Sending emails with Symfony is pretty straightforward thanks to the SwiftmailerBundle, which leverages the power of the Swift Mailer library.
To functionally test that an email was sent, and even assert the email subject, content or any other headers, you can use the Symfony Profiler.
Start with a controller action that sends an email:
public function sendEmail($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
->setTo('recipient@example.com')
->setBody('You should see me from the profiler!')
;
$mailer->send($message);
// ...
}
In your functional test, use the swiftmailer
collector on the profiler
to get information about the messages sent on the previous request:
// tests/Controller/MailControllerTest.php
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MailControllerTest extends WebTestCase
{
public function testMailIsSentAndContentIsOk()
{
$client = static::createClient();
// enables the profiler for the next request (it does nothing if the profiler is not available)
$client->enableProfiler();
$crawler = $client->request('POST', '/path/to/above/action');
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
// checks that an email was sent
$this->assertSame(1, $mailCollector->getMessageCount());
$collectedMessages = $mailCollector->getMessages();
$message = $collectedMessages[0];
// Asserting email data
$this->assertInstanceOf('Swift_Message', $message);
$this->assertSame('Hello Email', $message->getSubject());
$this->assertSame('send@example.com', key($message->getFrom()));
$this->assertSame('recipient@example.com', key($message->getTo()));
$this->assertSame(
'You should see me from the profiler!',
$message->getBody()
);
}
}
Troubleshooting¶
Problem: The Collector Object Is null
¶
The email collector is only available when the profiler is enabled and collects information, as explained in How to Use the Profiler in a Functional Test.
Problem: The Collector Doesn’t Contain the Email¶
If a redirection is performed after sending the email (for example when you send an email after a form is processed and before redirecting to another page), make sure that the test client doesn’t follow the redirects, as explained in Testing. Otherwise, the collector will contain the information of the redirected page and the email won’t be accessible.