diff --git a/_build/redirection_map b/_build/redirection_map index 7e8c54bd737..9de3db1ba4d 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -417,3 +417,6 @@ /workflow/state-machines /workflow/introduction /workflow/usage /workflow /introduction/from_flat_php_to_symfony2 /introduction/from_flat_php_to_symfony +/email/dev_environment /mailer +/email/spool /mailer +/email/testing /mailer diff --git a/components/mime.rst b/components/mime.rst index 8c822772b2a..5b258080bcc 100644 --- a/components/mime.rst +++ b/components/mime.rst @@ -58,128 +58,64 @@ methods to compose the entire email message:: This only purpose of this component is to create the email messages. Use the :doc:`Mailer component ` to actually send them. In Symfony -applications, it's easier to use the :doc:`Mailer integration `. +applications, it's easier to use the :doc:`Mailer integration `. -Email Addresses ---------------- +Most of the details about how to create Email objects, including Twig integration, +can be found in the :doc:`Mailer documentation `. -All the methods that require email addresses (``from()``, ``to()``, etc.) accept -both strings and objects:: - - // ... - use Symfony\Component\Mime\Address; - use Symfony\Component\Mime\NamedAddress; - - $email = (new Email()) - // email address as a simple string - ->from('fabien@symfony.com') - - // email address as an object - ->from(new Address('fabien@symfony.com')) - - // email address as an object (email clients will display the name - // instead of the email address) - ->from(new NamedAddress('fabien@symfony.com', 'Fabien')) - - // ... - ; - -Multiple addresses are defined with the ``addXXX()`` methods:: - - $email = (new Email()) - ->to('foo@example.com') - ->addTo('bar@example.com') - ->addTo('baz@example.com') - - // ... - ; - -Alternatively, you can pass multiple addresses to each method:: - - $toAddresses = ['foo@example.com', new Address('bar@example.com')]; - - $email = (new Email()) - ->to(...$toAddresses) - ->cc('cc1@example.com', 'cc2@example.com') - - // ... - ; - -Message Contents +Twig Integration ---------------- -The text and HTML contents of the email messages can be strings (usually the -result of rendering some template) or PHP resources:: +The Mime component comes with excellent integration with Twig, allowing you to +create messages from Twig templates, embed images, inline CSS and more. Details +on how to use those features can be found in the Mailer documentation: +:ref:`Twig: HTML & CSS `. - $email = (new Email()) - // ... - // simple contents defined as a string - ->text('Lorem ipsum...') - ->html('

Lorem ipsum...

') +But if you're using the Mime component without the Symfony framework, you'll need +to handle a few setup details. - // contents obtained from a PHP resource - ->text(fopen('/path/to/emails/user_signup.txt', 'r')) - ->html(fopen('/path/to/emails/user_signup.html', 'r')) - ; +Twig Setup +~~~~~~~~~~ -.. tip:: +To integrate with Twig, use the :class:`Symfony\\Bridge\\Twig\\Mime\\BodyRenderer` +class to render the template and update the email message contents with the results:: - You can also use Twig templates to render the HTML and text contents. Read - the :ref:`mime-component-twig-integration` section later in this article to - learn more. + // ... + use Symfony\Bridge\Twig\Mime\BodyRenderer; + use Twig\Environment; + use Twig\Loader\FilesystemLoader; -Embedding Images ----------------- + // when using the Mime component inside a full-stack Symfony application, you + // don't need to do this Twig setup. You only have to inject the 'twig' service + $loader = new FilesystemLoader(__DIR__.'/templates'); + $twig = new Environment($loader); -If you want to display images inside your email contents, you must embed them -instead of adding them as attachments. When using Twig to render the email -contents, as explained :ref:`later in this article ` -the images are embedded automatically. Otherwise, you need to embed them manually. + $renderer = new BodyRenderer($twig); + // this updates the $email object contents with the result of rendering + // the template defined earlier with the given context + $renderer->render($email); -First, use the ``embed()`` or ``embedFromPath()`` method to add an image from a -file or resource:: +Inlining CSS Styles (and other Extensions) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - $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') - ; +To use the :ref:`inline_css ` filter, first install the Twig +extension: -The second optional argument of both methods is the image name ("Content-ID" in -the MIME standard). Its value is an arbitrary string used later to reference the -images 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(' ... ...') - ; +.. code-block:: terminal -File Attachments ----------------- + $ composer require twig/cssinliner-extension -Use the ``attachFromPath()`` method to attach files that exist in your file system:: +Now, enable the extension:: - $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') - ; + // ... + use Twig\CssInliner\CssInlinerExtension; -Alternatively you can use the ``attach()`` method to attach contents generated -with PHP resources:: + $loader = new FilesystemLoader(__DIR__.'/templates'); + $twig = new Environment($loader); + $twig->addExtension(new CssInlinerExtension()); - $email = (new Email()) - // ... - ->attach(fopen('/path/to/documents/contract.doc', 'r')) - ; +The same process should be used for enabling other extensions, like the +:ref:`MarkdownExtension ` and :ref:`InkyExtension `. Creating Raw Email Messages --------------------------- @@ -287,285 +223,6 @@ from their serialized contents:: // later, recreate the original message to actually send it $message = new RawMessage(unserialize($serializedEmail)); -.. _mime-component-twig-integration: - -Twig Integration ----------------- - -The Mime component integrates with the :doc:`Twig template engine ` -to provide advanced features such as CSS style inlining and support for HTML/CSS -frameworks to create complex HTML email messages. - -Rendering Email Contents with Twig -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you define the contents of your email in Twig templates, use the -:class:`Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail` class. This class extends -from the :class:`Symfony\\Component\\Mime\\Email` class explained above and adds -some utility methods for Twig templates:: - - use Symfony\Bridge\Twig\Mime\TemplatedEmail; - - $email = (new TemplatedEmail()) - ->from('fabien@symfony.com') - ->to('foo@example.com') - // ... - - // this method defines the path of the Twig template to render - ->htmlTemplate('messages/user/signup.html.twig') - - // this method defines the parameters (name => value) passed to templates - ->context([ - 'expiration_date' => new \DateTime('+7 days'), - 'username' => 'foo', - ]) - ; - -Once the email object has been created, you must set up Twig to define where -templates are located and then, use the -:class:`Symfony\\Bridge\\Twig\\Mime\\BodyRenderer` class to render the template -and update the email message contents with the results. All this is done -automatically when using the component inside a Symfony application:: - - // ... - use Symfony\Bridge\Twig\Mime\BodyRenderer; - use Twig\Environment; - use Twig\Loader\FilesystemLoader; - - // when using the Mime component inside a full-stack Symfony application, you - // don't need to do this Twig setup. You only have to inject the 'twig' service - $loader = new FilesystemLoader(__DIR__.'/templates'); - $twig = new Environment($loader); - - $renderer = new BodyRenderer($twig); - // this updates the $email object contents with the result of rendering - // the template defined earlier with the given context - $renderer->render($email); - -The last step is to create the Twig template used to render the contents: - -.. code-block:: html+twig - -

Welcome {{ username }}!

- -

You signed up to our site using the following email:

-

{{ email.to }}

- -

Click here to activate your account

- -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 called -``email``. This variable is an instance of the -:class:`Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail` class which gives -access to some of the email message properties. - -When the text content of the message is not defined explicitly, the -``BodyRenderer()`` class generates it automatically converting the HTML contents -into text. If you have `league/html-to-markdown`_ installed in your application, -it uses that to turn HTML into Markdown. Otherwise, it applies the -:phpfunction:`strip_tags` PHP function to the original HTML contents. - -If you prefer to define the text content yourself, use the ``text()`` method -explained in the previous sections or the ``textTemplate()`` method provided by -the ``TemplatedEmail`` class:: - - use Symfony\Bridge\Twig\Mime\TemplatedEmail; - - $email = (new TemplatedEmail()) - ->from('fabien@symfony.com') - ->to('foo@example.com') - // ... - - ->textTemplate('messages/user/signup.txt.twig') - ->htmlTemplate('messages/user/signup.html.twig') - - ->context([ - 'expiration_date' => new \DateTime('+7 days'), - 'username' => 'foo', - ]) - ; - -.. _embedding-images-in-emails-with-twig: - -Embedding Images in Emails with Twig -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of dealing with the ```` syntax explained in the -previous sections, when using Twig to render email contents you can refer to -image files as usual. First, define a Twig namespace called ``images`` to -simplify things later:: - - // ... - - $templateLoader = new FilesystemLoader(__DIR__.'/templates'); - $templateLoader->addPath(__DIR__.'/images', 'images'); - $twig = new Environment($templateLoader); - -Now, use the special ``email.image()`` Twig helper to embed the images inside -the email contents: - -.. code-block:: html+twig - - {# '@images/' refers to the Twig namespace defined earlier #} - - -

Welcome {{ username }}!

- {# ... #} - -Inlining CSS Styles in Emails with Twig -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Designing the HTML contents of an email is very different from designing a -normal HTML page. For starters, most email clients only support a subset of all -CSS features. In addition, popular email clients such as Gmail don't support -defining styles inside ```` sections and you must **inline -all the CSS styles**. - -CSS inlining means that every HTML tag must define a ``style`` attribute with -all its CSS styles. This not only increases the email byte size significantly -but also makes it impossible to manage for complex emails. That's why Twig -provides a ``CssInlinerExtension`` that automates everything for you. First, -install the Twig extension in your application: - -.. code-block:: terminal - - $ composer require twig/cssinliner-extension - -Now, enable the extension (this is done automatically in Symfony applications):: - - // ... - use Twig\CssInliner\CssInlinerExtension; - - $loader = new FilesystemLoader(__DIR__.'/templates'); - $twig = new Environment($loader); - $twig->addExtension(new CssInlinerExtension()); - -Finally, wrap the entire template contents with the ``inline_css`` filter: - -.. code-block:: html+twig - - {% filter inline_css %} - - -

Welcome {{ username }}!

- {# ... #} - {% endfilter %} - -You can also define some or all CSS styles in external files and pass them as -arguments of the filter: - -.. code-block:: html+twig - - {# '@css/' refers to the Twig namespace defined earlier #} - {% filter inline_css('@css/mailing.css') %} - - -

Welcome {{ username }}!

- {# ... #} - {% endfilter %} - -Rendering Markdown Contents in Emails with Twig -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Twig provides another extension called ``MarkdownExtension`` that lets you -define the email contents using the `Markdown syntax`_. In addition to the -extension, you must also install a Markdown conversion library (the extension is -compatible with all the popular libraries): - -.. code-block:: terminal - - $ composer require twig/markdown-extension - - # these libraries are compatible too: erusev/parsedown, michelf/php-markdown - $ composer require league/commonmark - -Now, enable the extension (this is done automatically in Symfony applications):: - - // ... - use Twig\Markdown\MarkdownExtension; - - $loader = new FilesystemLoader(__DIR__.'/templates'); - $twig = new Environment($loader); - $twig->addExtension(new MarkdownExtension()); - -Finally, use the ``markdown`` filter to convert parts or the entire email -contents from Markdown to HTML: - -.. code-block:: twig - - {% filter markdown %} - Welcome {{ username }}! - ======================= - - You signed up to our site using the following email: - `{{ email.to }}` - - [Click here to activate your account]({{ url('...') }}) - {% endfilter %} - -Using the Inky Email Templating Language with Twig -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Creating beautifully designed emails that work on every email client is so -complex that there are HTML/CSS frameworks dedicated to that. One of the most -popular frameworks is called `Inky`_. It defines a syntax based on some simple -tags which are later transformed into the real HTML code sent to users: - -.. code-block:: html - - - - - This is a column. - - - -Twig provides integration with Inky via the ``InkyExtension``. First, install -the extension in your application: - -.. code-block:: terminal - - $ composer require twig/inky-extension - -Now, enable the extension (this is done automatically in Symfony applications):: - - // ... - use Twig\Inky\InkyExtension; - - $loader = new FilesystemLoader(__DIR__.'/templates'); - $twig = new Environment($loader); - $twig->addExtension(new InkyExtension()); - -Finally, use the ``inky`` filter to convert parts or the entire email -contents from Inky to HTML: - -.. code-block:: html+twig - - {% filter inky %} - - - - -

Welcome {{ username }}!

-
- - {# ... #} -
-
- {% endfilter %} - -You can combine all filters to create complex email messages: - -.. code-block:: twig - - {% filter inky|inline_css(source('@zurb/stylesheets/main.css')) %} - {# ... #} - {% endfilter %} - MIME Types Utilities -------------------- @@ -638,8 +295,5 @@ You can register your own MIME type guesser by creating a class that implements } .. _`MIME`: https://en.wikipedia.org/wiki/MIME -.. _`league/html-to-markdown`: https://github.com/thephpleague/html-to-markdown -.. _`Markdown syntax`: https://commonmark.org/ -.. _`Inky`: https://foundation.zurb.com/emails.html .. _`MIME types`: https://en.wikipedia.org/wiki/Media_type .. _`fileinfo extension`: https://php.net/fileinfo diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst index d738484c6b8..6559ae15c0a 100644 --- a/console/command_in_controller.rst +++ b/console/command_in_controller.rst @@ -21,7 +21,7 @@ their code. Instead, you can execute the command directly. overhead. Imagine you want to send spooled Swift Mailer messages by -:doc:`using the swiftmailer:spool:send command `. +:doc:`using the swiftmailer:spool:send command `. Run this command from inside your controller via:: // src/Controller/SpoolController.php diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst index 9fcdebd69f7..493be6b3fce 100644 --- a/contributing/documentation/format.rst +++ b/contributing/documentation/format.rst @@ -131,7 +131,7 @@ If you want to modify that title, use this alternative syntax: .. code-block:: rst - :doc:`Spooling Email ` + :doc:`Doctrine Associations ` .. note:: diff --git a/email.rst b/email.rst index 29bb7a1d171..f1ef02ef9bd 100644 --- a/email.rst +++ b/email.rst @@ -1,8 +1,13 @@ .. index:: single: Emails -How to Send an Email -==================== +Swift Mailer +============ + +.. note:: + + In Symfony 4.3, the :doc:`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 @@ -158,16 +163,498 @@ file. For example, for `Amazon SES`_ (Simple Email Service): Use the same technique for other mail services, as most of the time there is nothing more to it than configuring an SMTP endpoint. -Learn more ----------- +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): + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/test/swiftmailer.yaml + swiftmailer: + disable_delivery: true + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // config/packages/test/swiftmailer.php + $container->loadFromExtension('swiftmailer', [ + 'disable_delivery' => "true", + ]); + +.. _sending-to-a-specified-address: + +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: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/dev/swiftmailer.yaml + swiftmailer: + delivery_addresses: ['dev@example.com'] + + .. code-block:: xml + + + + + + + dev@example.com + + + + .. code-block:: php + + // 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( + 'HelloBundle: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: + +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: + +.. configuration-block:: + + .. code-block:: yaml + + # 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$/' + + .. code-block:: xml + + + + + + + + /@specialdomain\.com$/ + /^admin@mydomain\.com$/ + dev@example.com + + + + .. code-block:: php + + // 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. + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/dev/web_profiler.yaml + web_profiler: + intercept_redirects: true + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // 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`_ + 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. + +.. _email-spool-memory: + +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: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/swiftmailer.yaml + swiftmailer: + # ... + spool: { type: memory } + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // config/packages/swiftmailer.php + $container->loadFromExtension('swiftmailer', [ + // ... + 'spool' => ['type' => 'memory'], + ]); + +.. _spool-using-a-file: + +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: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/swiftmailer.yaml + swiftmailer: + # ... + spool: + type: file + path: /path/to/spooldir + + .. code-block:: xml + + + + + + + + + + + .. code-block:: php + + // 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: + + .. code-block:: yaml + + 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: + +.. code-block:: terminal + + $ APP_ENV=prod php bin/console swiftmailer:spool:send + +It has an option to limit the number of messages to be sent: + +.. code-block:: terminal + + $ APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=10 + +You can also set the time limit in seconds: + +.. code-block:: terminal + + $ 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_``. + + 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, the message will not be unserializable. + + On the next execution of ``swiftmailer:spool:send`` an error will raise because + the class ``Swift_Message_`` doesn't exist (anymore). + + The solutions are either to use the memory spool or to load the + ``swiftmailer`` service without the ``lazy`` option (see :doc:`/service_container/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 :doc:`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 :doc:`/testing/profiling`. -.. toctree:: - :maxdepth: 1 +Problem: The Collector Doesn't Contain the Email +................................................ - email/dev_environment - email/spool - email/testing +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 +:doc:`/testing`. Otherwise, the collector will contain the information of the +redirected page and the email won't be accessible. +.. _`MailCatcher`: https://github.com/sj26/mailcatcher +.. _`MailHog`: https://github.com/mailhog/MailHog .. _`Swift Mailer`: http://swiftmailer.org/ .. _`SwiftMailerBundle`: https://github.com/symfony/swiftmailer-bundle .. _`Creating Messages`: https://swiftmailer.symfony.com/docs/messages.html diff --git a/email/dev_environment.rst b/email/dev_environment.rst deleted file mode 100644 index 2116618a7d0..00000000000 --- a/email/dev_environment.rst +++ /dev/null @@ -1,252 +0,0 @@ -.. index:: - single: Emails; In development - -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): - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/test/swiftmailer.yaml - swiftmailer: - disable_delivery: true - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // config/packages/test/swiftmailer.php - $container->loadFromExtension('swiftmailer', [ - 'disable_delivery' => "true", - ]); - -.. _sending-to-a-specified-address: - -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: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/dev/swiftmailer.yaml - swiftmailer: - delivery_addresses: ['dev@example.com'] - - .. code-block:: xml - - - - - - - dev@example.com - - - - .. code-block:: php - - // 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( - 'HelloBundle: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: - -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: - -.. configuration-block:: - - .. code-block:: yaml - - # 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$/' - - .. code-block:: xml - - - - - - - - /@specialdomain\.com$/ - /^admin@mydomain\.com$/ - dev@example.com - - - - .. code-block:: php - - // 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. - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/dev/web_profiler.yaml - web_profiler: - intercept_redirects: true - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // 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`_ - and `MailHog`_. - -.. _`MailCatcher`: https://github.com/sj26/mailcatcher -.. _`MailHog`: https://github.com/mailhog/MailHog diff --git a/email/spool.rst b/email/spool.rst deleted file mode 100644 index 47d96c00cf4..00000000000 --- a/email/spool.rst +++ /dev/null @@ -1,164 +0,0 @@ -.. index:: - single: Emails; Spooling - -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. - -.. _email-spool-memory: - -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: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/swiftmailer.yaml - swiftmailer: - # ... - spool: { type: memory } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // config/packages/swiftmailer.php - $container->loadFromExtension('swiftmailer', [ - // ... - 'spool' => ['type' => 'memory'], - ]); - -.. _spool-using-a-file: - -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: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/swiftmailer.yaml - swiftmailer: - # ... - spool: - type: file - path: /path/to/spooldir - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // 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: - - .. code-block:: yaml - - 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: - -.. code-block:: terminal - - $ APP_ENV=prod php bin/console swiftmailer:spool:send - -It has an option to limit the number of messages to be sent: - -.. code-block:: terminal - - $ APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=10 - -You can also set the time limit in seconds: - -.. code-block:: terminal - - $ 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_``. - - 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, the message will not be unserializable. - - On the next execution of ``swiftmailer:spool:send`` an error will raise because - the class ``Swift_Message_`` doesn't exist (anymore). - - The solutions are either to use the memory spool or to load the - ``swiftmailer`` service without the ``lazy`` option (see :doc:`/service_container/lazy_services`). diff --git a/email/testing.rst b/email/testing.rst deleted file mode 100644 index 8ca65047d9e..00000000000 --- a/email/testing.rst +++ /dev/null @@ -1,85 +0,0 @@ -.. index:: - single: Emails; Testing - -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 :doc:`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 :doc:`/testing/profiling`. - -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 -:doc:`/testing`. Otherwise, the collector will contain the information of the -redirected page and the email won't be accessible. - -.. _`Swift Mailer`: http://swiftmailer.org/ diff --git a/index.rst b/index.rst index df3e3ac12e7..be3dd71ae07 100644 --- a/index.rst +++ b/index.rst @@ -42,6 +42,7 @@ Topics frontend http_cache logging + mailer mercure messenger performance diff --git a/mailer.rst b/mailer.rst new file mode 100644 index 00000000000..5e5534496cd --- /dev/null +++ b/mailer.rst @@ -0,0 +1,649 @@ +Sending Emails with Mailer +========================== + +.. versionadded:: 4.3 + The Mailer component was added in Symfony 4.3 and is currently experimental. + The previous solution - Swift Mailer - is still valid: :doc:`Swift Mailer`. + +Installation +------------ + +.. caution:: + + The Mailer component is experimental in Symfony 4.3: some backwards compatibility + breaks could occur before 4.4. + +Symfony's Mailer & :doc:`Mime ` components form a *powerful* system +for creating and sending emails - complete with support for multipart messages, Twig +integration, CSS inlining, file attachments and a lot more. Get them installed with: + +.. code-block:: terminal + + $ composer require symfony/mailer + +Transport Setup +--------------- + +Emails are delivered via a "transport". And without installing anything else, you +can deliver emails over ``smtp`` by configuring your ``.env`` file: + +.. code-block:: bash + + # .env + MAILER_DSN=smtp://user:pass@smtp.example.com + +Using a 3rd Party Transport +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +But an easier option is to send emails via a 3rd party provider. Mailer supports +several - install whichever you want: + +================== ============================================= +Service Install with +================== ============================================= +Amazon SES ``composer require symfony/amazon-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`` +Gmail ``composer require symfony/google-mailer`` +================== ============================================= + +Each library includes a :ref:`Flex recipe ` that will add example configuration +to your ``.env`` file. For example, suppose you want to use SendGrid. First, +install it: + +.. code-block:: terminal + + $ composer require symfony/sendgrid-mailer + +You'll now have a new line in your ``.env`` file that you can uncomment: + +.. code-block:: bash + + # .env` + + SENDGRID_KEY= + MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid + +The``MAILER_DSN`` isn't a *real* SMTP address: it's a simple format that offloads +most of the configuration work to mailer. The ``@sendgrid`` part of the address +activates the SendGrid mailer library that you just installed, which knows all +about 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 use +to configure the *actual* address and authentication for delivery. Some also have +options 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. + +Creating & Sending Messages +--------------------------- + +To send an email, autowire the mailer using +:class:`Symfony\\Component\\Mailer\\MailerInterface` (service id ``mailer``) +and create an :class:`Symfony\\Component\\Mime\\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('hello@example.com') + ->to('you@example.com') + //->cc('cc@example.com') + //->bcc('bcc@example.com') + //->replyTo('fabien@example.com') + //->priority(Email::PRIORITY_HIGH) + ->subject('Time for Symfony Mailer!') + ->text('Sending emails is fun again!') + ->html('

See Twig integration for better HTML integration!

'); + + $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.) accept +both strings or address objects:: + + // ... + use Symfony\Component\Mime\Address; + use Symfony\Component\Mime\NamedAddress; + + $email = (new Email()) + // email address as a simple string + ->from('fabien@example.com') + + // email address as an object + ->from(new Address('fabien@example.com')) + + // email address as an object (email clients will display the name + // instead of the email address) + ->from(new NamedAddress('fabien@example.com', 'Fabien')) + + // ... + ; + +Multiple addresses are defined with the ``addXXX()`` methods:: + + $email = (new Email()) + ->to('foo@example.com') + ->addTo('bar@example.com') + ->addTo('baz@example.com') + + // ... + ; + +Alternatively, you can pass multiple addresses to each method:: + + $toAddresses = ['foo@example.com', new Address('bar@example.com')]; + + $email = (new Email()) + ->to(...$toAddresses) + ->cc('cc1@example.com', 'cc2@example.com') + + // ... + ; + +Message Contents +~~~~~~~~~~~~~~~~ + +The text and HTML contents of the email messages can be strings (usually the +result of rendering some template) or PHP resources:: + + $email = (new Email()) + // ... + // simple contents defined as a string + ->text('Lorem ipsum...') + ->html('

Lorem ipsum...

') + + // 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. Read + the `Twig: HTML & CSS`_ section later in this article to + learn 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') + ; + +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 them +instead of adding them as attachments. When using Twig to render the email +contents, 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 a +file 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" in +the MIME standard). Its value is an arbitrary string used later to reference the +images 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(' ... ...') + ; + +Global from Address +------------------- + +Instead of calling ``->from()`` *every* time you create a new email, you can +create an event subscriber to set it automatically:: + + // src/EventListener/MailerFromListener.php + namespace App\EventListener; + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\Mailer\Event\MessageEvent; + use Symfony\Component\Mime\Email; + + class MailerFromListener implements EventSubscriberInterface + { + public function onMessageSend(MessageEvent $event) + { + $message = $event->getMessage(); + + // make sure it's an Email object + if (!$message instanceof Email) { + return; + } + + // always set the from address + $message->from('fabien@example.com'); + } + + public static function getSubscribedEvents() + { + return [MessageEvent::class => 'onMessageSend']; + } + } + +.. _mailer-twig: + +Twig: HTML & CSS +---------------- + +The Mime component integrates with the :doc:`Twig template engine ` +to provide advanced features such as CSS style inlining and support for HTML/CSS +frameworks to create complex HTML email messages. First, make sure Twig is instaled: + +.. code-block:: terminal + + $ composer require symfony/twig-bundle + +HTML Content +~~~~~~~~~~~~ + +To define the contents of your email with Twig, use the +:class:`Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail` class. This class extends +the normal :class:`Symfony\\Component\\Mime\\Email` class but adds some new methods +for Twig templates:: + + use Symfony\Bridge\Twig\Mime\TemplatedEmail; + + $email = (new TemplatedEmail()) + ->from('fabien@example.com') + ->to(new NamedAddress('ryan@example.com', '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: + +.. code-block:: html+twig + + {# templates/emails/signup.html.twig #} +

Welcome {{ email.toName }}!

+ +

+ You signed up as {{ username }} the following email: +

+

{{ email.to[0].address }}

+ +

+ Click here to activate your account + (this link is valid until {{ expiration_date|date('F jS') }}) +

+ +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 called +``email``, which is an instance of +:class:`Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail`. + +Text Content +~~~~~~~~~~~~ + +When the text content of a ``TemplatedEmail`` is not explicitly defined, mailer +will generate it automatically by converting the HTML contents into text. If you +have `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 :phpfunction:`strip_tags` PHP function to the original +HTML contents. + +If you want to define the text content yourself, use the ``text()`` method +explained in the previous sections or the ``textTemplate()`` method provided by +the ``TemplatedEmail`` class: + +.. code-block:: diff + + + 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 ```` syntax explained in the +previous sections, when using Twig to render email contents you can refer to +image files as usual. First, to simplify things, define a Twig namespace called +``images`` that points to whatever directory your images are stored in: + +.. code-block:: yaml + + # config/packages/twig.yaml + twig: + # ... + + paths: + # point this wherever your images live + images: '%kernel.project_dir%/assets/images' + +Now, use the special ``email.image()`` Twig helper to embed the images inside +the email contents: + +.. code-block:: html+twig + + {# '@images/' refers to the Twig namespace defined earlier #} + Logo + +

Welcome {{ email.toName }}!

+ {# ... #} + +.. _mailer-inline-css: + +Inlining CSS Styles +~~~~~~~~~~~~~~~~~~~ + +Designing the HTML contents of an email is very different from designing a +normal HTML page. For starters, most email clients only support a subset of all +CSS features. In addition, popular email clients like Gmail don't support +defining styles inside ```` sections and you must **inline +all the CSS styles**. + +CSS inlining means that every HTML tag must define a ``style`` attribute with +all its CSS styles. This can make organizing your CSS a mess. That's why Twig +provides a ``CssInlinerExtension`` that automates everything for you. Install +it with: + +.. code-block:: terminal + + $ composer require twig/cssinliner-extension + +The extension is enabled automatically. To use this, wrap the entire template +with the ``inline_css`` filter: + +.. code-block:: html+twig + + {% filter inline_css %} + + +

Welcome {{ email.toName }}!

+ {# ... #} + {% endfilter %} + +Using External CSS Files +........................ + +You can also define CSS styles in external files and pass them as +arguments to the filter: + +.. code-block:: html+twig + + {% filter inline_css(source('@css/email.css')) %} +

Welcome {{ username }}!

+ {# ... #} + {% endfilter %} + +You can pass unlimited number of arguments to ``inline_css()`` to load multiple +CSS files. For this example to work, you also need to define a new Twig namespace +called ``css`` that points to the directory where ``email.css`` lives: + +.. _mailer-css-namespace: + +.. code-block:: yaml + + # config/packages/twig.yaml + twig: + # ... + + paths: + # point this wherever your css files live + css: '%kernel.project_dir%/assets/css' + +.. _mailer-markdown: + +Rendering Markdown Content +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Twig provides another extension called ``MarkdownExtension`` that lets you +define the email contents using `Markdown syntax`_. To use this, install the +extension and a Markdown conversion library (the extension is compatible with +several popular libraries): + +.. code-block:: terminal + + # 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 or +the entire email contents from Markdown to HTML: + +.. code-block:: twig + + {% filter 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('...') }}) + {% endfilter %} + +.. _mailer-inky: + +Inky Email Templating Language +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creating beautifully designed emails that work on every email client is so +complex that there are HTML/CSS frameworks dedicated to that. One of the most +popular frameworks is called `Inky`_. It defines a syntax based on some simple +tags which are later transformed into the real HTML code sent to users: + +.. code-block:: html + + + + + This is a column. + + + +Twig provides integration with Inky via the ``InkyExtension``. First, install +the extension in your application: + +.. code-block:: terminal + + $ composer require twig/inky-extension + +The extension adds an ``inky`` filter, which can be used to convert parts or the +entire email contents from Inky to HTML: + +.. code-block:: html+twig + + {% filter inky %} + + + + +

Welcome {{ email.toName }}!

+
+ + {# ... #} +
+
+ {% endfilter %} + +You can combine all filters to create complex email messages: + +.. code-block:: twig + + {% filter inky|inline_css(source('@css/foundation-emails.css')) %} + {# ... #} + {% endfilter %} + +This makes use of the :ref:`css Twig namespace ` we created +earlier. You could, for example, `download the foundation-emails.css file`_ +directly 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 :doc:`Messenger ` to send +the messages later via a Messenger transport. + +Start by following the :doc:`Messenger ` documentation and configuring +a transport. Once everything is set up, when you call ``$mailer->send()``, a +:class:`Symfony\\Component\\Mailer\\Messenger\\SendEmailMessage` message will +be dispatched through the default message bus (``messenger.default_bus``). Assuming +you have a transport called ``async``, you can route the message there: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + async: "%env(MESSENGER_TRANSPORT_DSN)%" + + routing: + 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: 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 to +the transport to be handled later (see :ref:`messenger-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: + +.. code-block:: yaml + + # 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 to +a specific address, instead of the *real* address. To do that, you can take +advantage of the ``EnvelopeListener`` and register it *only* for the ``dev`` +environment: + +.. code-block:: yaml + + # config/services_dev.yaml + services: + mailer.dev.set_recipients: + class: Symfony\Component\Mailer\EventListener\EnvelopeListener + tags: ['kernel.event_subscriber'] + arguments: + $sender: null + $recipients: ['youremail@example.com'] + +.. _`download the foundation-emails.css file`: https://github.com/zurb/foundation-emails/blob/develop/dist/foundation-emails.css +.. _`league/html-to-markdown`: https://github.com/thephpleague/html-to-markdown +.. _`Markdown syntax`: https://commonmark.org/ +.. _`Inky`: https://foundation.zurb.com/emails.html diff --git a/reference/configuration/swiftmailer.rst b/reference/configuration/swiftmailer.rst index d53935b8d59..8a6a2cd77e8 100644 --- a/reference/configuration/swiftmailer.rst +++ b/reference/configuration/swiftmailer.rst @@ -157,7 +157,7 @@ values are ``plain``, ``login``, ``cram-md5``, or ``null``. spool ~~~~~ -For details on email spooling, see :doc:`/email/spool`. +For details on email spooling, see :doc:`/mailer`. type .... diff --git a/setup/flex.rst b/setup/flex.rst index 055cd30d015..328f13a098b 100644 --- a/setup/flex.rst +++ b/setup/flex.rst @@ -51,6 +51,8 @@ SwiftmailerBundle and returns the "recipe" for it. Flex keeps tracks of the recipes it installed in a ``symfony.lock`` file, which must be committed to your code repository. +.. _flex-recipe: + Symfony Flex Recipes ~~~~~~~~~~~~~~~~~~~~