Slide 1

Slide 1 text

Back to the basics... part 1/n Fabien Potencier @fabpot

Slide 2

Slide 2 text

1) Emails reloaded Fabien Potencier @fabpot

Slide 3

Slide 3 text

The state of sending emails with Symfony Swiftmailer is the de-facto library used for sending emails I’m the maintainer since 2009 Code infrastructure is still the same as version 4.0, roughly released at the same time as Symfony 1.2 … code is getting old!

Slide 4

Slide 4 text

Swiftmailer is not “standard” PHP … very different from Symfony’s way of writing PHP code … which is not a “big” problem as developers rarely need to interact with Swiftmailer internals… except for me, the maintainer … which means that Swiftmailer is not really maintained

Slide 5

Slide 5 text

Swiftmailer is not “standard” PHP It’s getting better 2014: switched tests to PHPUnit 2017: version 6 release Removed some interfaces But really just cosmetic changes (nothing too radical)

Slide 6

Slide 6 text

Swiftmailer is not “standard” PHP No namespaces Specific boot sequence (heavy) “Proprietary” dependency injection container “Proprietary” event dispatcher

Slide 7

Slide 7 text

Swiftmailer is not “standard” PHP Composer autoloader is not used

Slide 8

Slide 8 text

Swiftmailer two main problems Weird and complex class inheritance to ease usage Message instances are not a data objects

Slide 9

Slide 9 text

Swiftmailer is not “standard” PHP Swift_Message Dependency Injection…

Slide 10

Slide 10 text

This is the state of Swiftmailer in 2018

Slide 11

Slide 11 text

Swiftmailer great features Plugins: RedirectingPlugin/ImpersonatePlugin/ThrottlerPlugin/… Auth: CRAM-MD5, Login, NTLM, Plain, XOAuth2 S/MIME signing and encrypting Punycode / UTF-8 headers SMTP pipelining …

Slide 12

Slide 12 text

Swiftmailer no so great features Bootstrapping Overengineering Spools

Slide 13

Slide 13 text

Swiftmailer missing features Proper Twig integration SMTP only, no support for email provider’s API

Slide 14

Slide 14 text

Symfony Mailer

Slide 15

Slide 15 text

For transactional emails

Slide 16

Slide 16 text

$email = (new Email()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setSubject('Some subject') ->setTextBody('Some message') ->attach(__DIR__.'/some.txt') ; $transport = Transport::fromDsn('smtp://localhost')); $transport->send($email); The basics

Slide 17

Slide 17 text

Email Transports

Slide 18

Slide 18 text

new SmtpTransport('localhost'); SMTP: Your server

Slide 19

Slide 19 text

Symfony\Component\Mailer\Transport\* new GmailTransport('user', 'pass'); new MailgunTransport('user', 'pass'); new SendgridTransport('key'); new PostmarkTransport('id'); … SMTP: Shortcuts

Slide 20

Slide 20 text

Symfony\Component\Mailer\Transport\Http\* new MailgunTransport('user', 'pass'); … HTTP: Raw message

Slide 21

Slide 21 text

Symfony\Component\Mailer\Transport\Http\Api\* new MailgunTransport('user', 'pass'); new PostmarkTransport('id'); … HTTP: Via API

Slide 22

Slide 22 text

Transport::fromDsn('...'); smtp://user:pass@gmail smtp://key@sendgrid smtp://null smtp://user:pass@mailgun http://key:domain@mailgun api://id@postmark Use a DSN

Slide 23

Slide 23 text

Transport::fromDsn('...'); Failover api://id@postmark || smtp://key@sendgrid LoadBalanced api://id@postmark && smtp://key@sendgrid Failover and LoadBalanced transports

Slide 24

Slide 24 text

Unified behaviour across all providers Same events for all transports Unified DSNs / env vars Easily switch from SMTP in dev to a "real" provider in prod / same API Same message interface and serialization

Slide 25

Slide 25 text

SMTP / HTTP / API? SMTP HTTP API Offline Yes via Mailcatcher No / Mock No / Mock Mailcatcher Yes No No Standard Yes No No Symfony generated Yes Yes No Fast No Yes Yes

Slide 26

Slide 26 text

$email = (new Email()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setSubject('Some subject') ->setTextBody('Some message') ->attach(__DIR__.'/some.txt') ; $transport = Transport::fromDsn('smtp://localhost')); $transport->send($email); The basics

Slide 27

Slide 27 text

Sending Emails With Twig

Slide 28

Slide 28 text

$email = (new TemplatedEmail()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setTemplate('emails/example.html.twig') ; $bus->dispatch($email); Native integration with Twig

Slide 29

Slide 29 text

{% block subject 'Email subject' %} {% block text %} Optional text representation {% endblock %} {% block html %} Some content using HTML {% endblock %} Emails via Twig Templates

Slide 30

Slide 30 text

{% block html %}

Some content using HTML

{% endblock %} Embedding images

Slide 31

Slide 31 text

$email = (new TemplatedEmail()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setTemplate('emails/example.html.twig') ->setContext(['name' => 'Fabien Potencier']) ; $bus->dispatch($email); Pass some context

Slide 32

Slide 32 text

{% block subject %}Email subject{% endblock %} {% block html %}

Welcome {{ email.context.name }}!

{% endblock %} {% block text %} Optional text representation {% endblock %} Use the context

Slide 33

Slide 33 text

But HTML in emails is hard Compatibility with email clients Inline CSS Responsive layout …

Slide 34

Slide 34 text

Responsive templates
 and default stylesheets
 courtesy of

Slide 35

Slide 35 text

Stylesheets managed by Encore Styles inlined via Symfony CSS selector

Slide 36

Slide 36 text

$email = (new ThemedEmail()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setTemplate('emails/example.html.twig') ; $bus->dispatch($email); Templates with a theme

Slide 37

Slide 37 text

{% extends email_layout() %} {% block subject %}Some simple email with style{% endblock %} {% block text %} Optional text representation {% endblock %} {% block content %}

Welcome to Elephpant Land!

We're very pleased to welcome you on-board.

And we're very proud of our on-boarding e-mails!

{% email_box %}

Sign-up now!

Get a new Elephpant photo in your inbox each day
... for only 4€ a month

{% email_button "https://somewhere.com/" %}Sign me up!{% endemail_button %} {% endemail_box %} {% endblock %} Responsive Emails

Slide 38

Slide 38 text

mailer: settings: from: [email protected] company: Elephpant Inc. product: Pink Elephpant

Slide 39

Slide 39 text

Some Title

Works with simple HTML tags.

  • Default styles are good enough;
  • Easy to override;
  • Responsive by default!
Theme: All clients and nicely styled!

Slide 40

Slide 40 text

{% markdown %} # Some Title Works with simple HTML tags. * Default styles are good enough; * Easy to override; * Responsive by default! {% endmarkdown %} Theme: HTML or Markdown

Slide 41

Slide 41 text

{% email_button "https://google.com" %}Google{% endemail_button %} Theme: Buttons

Slide 42

Slide 42 text

{% email_button "https://google.com" { color: "red" } %} Theme: Buttons

Slide 43

Slide 43 text

{% email_container %}

Container Title

Some random content in the container.

{% endemail_container %} Theme: Containers

Slide 44

Slide 44 text

{% markdown %} | Version | LTS? | Latest | | ------------- |:-------------:| -------:| | 1.0 | Yes | 1.0.1 | | 2.1 | No | 2.1.33 | {% endmarkdown %}
Theme: Markdown

Slide 45

Slide 45 text

{% email_box %}

Great Discount!

20% off just for you because I love you...

{% email_button "https://…" %}Purchase now!{% endemail_button %} {% endemail_box %} Theme: Boxes

Slide 46

Slide 46 text

namespace App\Email; use Symfony\Component\Mailer\Theming\TemplatedEmail; class PromotionEmail extends TemplatedEmail { private $rate; public function setDiscountRate(int $rate): self { $this->rate = $rate; return $this; } public function getDiscountRate(): int { return $this->rate; } public function getTemplate(): string { return 'emails/promotion.html.twig'; } } Package your emails in a class

Slide 47

Slide 47 text

$email = (new PromotionEmail()) ->setTo('[email protected]') ->setDiscountRate(30) ; $transport->send($email); {% block html %}

Welcome to Elephpant Land!

Your discount rate is {{ email.discountRate }}%

{% endblock %} Email object in templates

Slide 48

Slide 48 text

$email = (new PasswordResetEmail()) ->setResetUrl('https://somewhere.com') ->setTo('[email protected]') ; $bus->dispatch($email); Built-in Standard Responsive Emails

Slide 49

Slide 49 text

Go async?

Slide 50

Slide 50 text

$transport = Transport::fromDsn('smtp://localhost')); $transport->send($email); $bus->dispatch($email); Messages via Messenger

Slide 51

Slide 51 text

public function email(Mailer $mailer) { $email = (new Email())->…; $mailer->send($email); return new Response('Sent'); } public function emailAsync(MessageBusInterface $bus) { $email = (new Email())->…; $bus->dispatch($email); return new Response('Sent'); } Messages via Messenger

Slide 52

Slide 52 text

public function emailAsync(MessageBusInterface $bus) { $email = (new Email())->…; $bus->dispatch($email); return new Response('Sent'); } Sync or Async, your choice

Slide 53

Slide 53 text

Emails are sent async via AMQP framework: messenger: routing: 'Symfony\Component\Mailer\Message\Message': amqp Emails are sent immediately framework: messenger: routing: #'Symfony\Component\Mailer\Message\Message': amqp Sync or Async, your choice

Slide 54

Slide 54 text

class MessageHandler { private $transport; public function __construct(TransportInterface $transport) { $this->transport = $transport; } public function __invoke(Email $email) { $this->transport->send($email); } } Here is the magic that makes it work!

Slide 55

Slide 55 text

Message is a data object

Slide 56

Slide 56 text

Digression… The Messenger Component ❤ ❤ ❤

Slide 57

Slide 57 text

Clicks, Bounces, Spam, and more…

Slide 58

Slide 58 text

email_webhooks: resource: '@MailerBundle/Resources/config/routing/webhooks.xml' prefix: /_email Webhooks: Mount the “universal” controller

Slide 59

Slide 59 text

class WebhookFailureHandler implements MessageHandlerInterface { private $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function __invoke(FailureWebhook $failure) { $this->logger->error('APP: New email failure', ['type' => $failure->getType(), 'email' => $failure->getEmail()]); } } Webhooks: Do something…

Slide 60

Slide 60 text

routing: 'Symfony\Component\Mailer\Webhook\FailureWebhook': amqp Webhooks: … sync or async

Slide 61

Slide 61 text

Leverages
 the Symfony ecosystem Twig Encore Serializer Messenger PSR logger CSS selector Event Dispatcher Dependency Injection Container Symfony Polyfills (punycode, intl, …) … with all the great features you love in Swiftmailer 6 Symfony Mailer

Slide 62

Slide 62 text

3 components 1 bundle still a work in progress will land in Symfony 4.3 Symfony Mailer

Slide 63

Slide 63 text

Thank you! ❤