Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Emails reloaded

Emails reloaded

Fabien Potencier

September 27, 2018

More Decks by Fabien Potencier

Other Decks in Technology


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

  2. 1) Emails reloaded Fabien Potencier @fabpot

  3. 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!
  4. 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
  5. 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)
  6. Swiftmailer is not “standard” PHP No namespaces Specific boot sequence

    (heavy) “Proprietary” dependency injection container “Proprietary” event dispatcher
  7. Swiftmailer is not “standard” PHP Composer autoloader is not used

  8. Swiftmailer two main problems Weird and complex class inheritance to

    ease usage Message instances are not a data objects
  9. Swiftmailer is not “standard” PHP Swift_Message Dependency Injection…

  10. This is the state of Swiftmailer in 2018

  11. 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 …
  12. Swiftmailer no so great features Bootstrapping Overengineering Spools

  13. Swiftmailer missing features Proper Twig integration SMTP only, no support

    for email provider’s API
  14. Symfony Mailer

  15. For transactional emails

  16. $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
  17. Email Transports

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

  19. Symfony\Component\Mailer\Transport\* new GmailTransport('user', 'pass'); new MailgunTransport('user', 'pass'); new SendgridTransport('key'); new

    PostmarkTransport('id'); … SMTP: Shortcuts
  20. Symfony\Component\Mailer\Transport\Http\* new MailgunTransport('user', 'pass'); … HTTP: Raw message

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

  22. Transport::fromDsn('...'); smtp://user:[email protected] smtp://[email protected] smtp://null smtp://user:[email protected] http://key:[email protected] api://[email protected] Use a DSN

  23. Transport::fromDsn('...'); Failover api://[email protected] || smtp://[email protected] LoadBalanced api://[email protected] && smtp://[email protected] Failover

    and LoadBalanced transports
  24. 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
  25. 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
  26. $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
  27. Sending Emails With Twig

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

    integration with Twig
  29. {% block subject 'Email subject' %} {% block text %}

    Optional text representation {% endblock %} {% block html %} Some content <b>using HTML</b> {% endblock %} Emails via Twig Templates
  30. {% block html %} <p>Some content <b>using HTML</b></p> <p><img src="{{

    email_image('@images/photo.jpg') }}"></p> {% endblock %} Embedding images
  31. $email = (new TemplatedEmail()) ->setFrom('[email protected]') ->setTo('[email protected]') ->setTemplate('emails/example.html.twig') ->setContext(['name' => 'Fabien

    Potencier']) ; $bus->dispatch($email); Pass some context
  32. {% block subject %}Email subject{% endblock %} {% block html

    %} <p>Welcome <b>{{ email.context.name }}</b>!</p> <p><img src="{{ email_image('@images/photo.jpg') }}"></p> {% endblock %} {% block text %} Optional text representation {% endblock %} Use the context
  33. But HTML in emails is hard Compatibility with email clients

    Inline CSS Responsive layout …
  34. Responsive templates
 and default stylesheets
 courtesy of

  35. Stylesheets managed by Encore Styles inlined via Symfony CSS selector

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

    with a theme
  37. {% extends email_layout() %} {% block subject %}Some simple email

    with style{% endblock %} {% block text %} Optional text representation {% endblock %} {% block content %} <h1>Welcome to Elephpant Land!</h1> <p>We're very pleased to welcome you on-board.</p> <p>And we're very proud of our on-boarding e-mails!</p> {% email_box %} <h1>Sign-up now!</h1> <p> Get a new Elephpant photo in your inbox each day<br> ... for only 4€ a month </p> {% email_button "https://somewhere.com/" %}Sign me up!{% endemail_button %} {% endemail_box %} {% endblock %} Responsive Emails
  38. mailer: settings: from: [email protected] company: Elephpant Inc. product: Pink Elephpant

  39. <h1>Some Title</h1> <p>Works with simple HTML tags.</p> <ul> <li>Default styles

    are good enough;</li> <li>Easy to override;</li> <li>Responsive by default!</li> </ul> Theme: All clients and nicely styled!
  40. {% markdown %} # Some Title Works with simple HTML

    tags. * Default styles are good enough; * Easy to override; * Responsive by default! {% endmarkdown %} Theme: HTML or Markdown
  41. {% email_button "https://google.com" %}Google{% endemail_button %} Theme: Buttons

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

  43. {% email_container %} <h1>Container Title</h1> <p>Some random content in the

    container.</p> {% endemail_container %} Theme: Containers
  44. <div class="table"> {% markdown %} | Version | LTS? |

    Latest | | ------------- |:-------------:| -------:| | 1.0 | Yes | 1.0.1 | | 2.1 | No | 2.1.33 | {% endmarkdown %} </div> Theme: Markdown
  45. {% email_box %} <h1>Great Discount!</h1> <p>20% off just for you

    because I love you...</p> {% email_button "https://…" %}Purchase now!{% endemail_button %} {% endemail_box %} Theme: Boxes
  46. 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
  47. $email = (new PromotionEmail()) ->setTo('[email protected]') ->setDiscountRate(30) ; $transport->send($email); {% block

    html %} <h1>Welcome to Elephpant Land!</h1> <p>Your discount rate is {{ email.discountRate }}%</p> {% endblock %} Email object in templates
  48. $email = (new PasswordResetEmail()) ->setResetUrl('https://somewhere.com') ->setTo('[email protected]') ; $bus->dispatch($email); Built-in Standard

    Responsive Emails
  49. Go async?

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

  51. 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
  52. public function emailAsync(MessageBusInterface $bus) { $email = (new Email())->…; $bus->dispatch($email);

    return new Response('Sent'); } Sync or Async, your choice
  53. 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
  54. class MessageHandler { private $transport; public function __construct(TransportInterface $transport) {

    $this->transport = $transport; } public function __invoke(Email $email) { $this->transport->send($email); } } <service id="mailer.messenger.email_handler" class="Symfony\Component\Mailer\Messenger\EmailHandler" public="false"> <argument type="service" id=“transport.transport" /> <tag name="messenger.message_handler" /> </service> Here is the magic that makes it work!
  55. Message is a data object

  56. Digression… The Messenger Component ❤ ❤ ❤

  57. Clicks, Bounces, Spam, and more…

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

  59. 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…
  60. routing: 'Symfony\Component\Mailer\Webhook\FailureWebhook': amqp Webhooks: … sync or async

  61. 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
  62. 3 components 1 bundle still a work in progress will

    land in Symfony 4.3 Symfony Mailer
  63. Thank you! ❤