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

Emails reloaded

Emails reloaded

Fabien Potencier

September 27, 2018
Tweet

More Decks by Fabien Potencier

Other Decks in Technology

Transcript

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

    (heavy) “Proprietary” dependency injection container “Proprietary” event dispatcher
  5. Swiftmailer two main problems Weird and complex class inheritance to

    ease usage Message instances are not a data objects
  6. 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 …
  7. $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
  8. 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
  9. 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
  10. $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
  11. {% block subject 'Email subject' %} {% block text %}

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

    email_image('@images/photo.jpg') }}"></p> {% endblock %} Embedding images
  13. {% 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
  14. {% 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
  15. <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!
  16. {% markdown %} # Some Title Works with simple HTML

    tags. * Default styles are good enough; * Easy to override; * Responsive by default! {% endmarkdown %} Theme: HTML or Markdown
  17. {% email_container %} <h1>Container Title</h1> <p>Some random content in the

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

    Latest | | ------------- |:-------------:| -------:| | 1.0 | Yes | 1.0.1 | | 2.1 | No | 2.1.33 | {% endmarkdown %} </div> Theme: Markdown
  19. {% 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
  20. 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
  21. $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
  22. 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
  23. 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
  24. 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!
  25. 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…
  26. 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
  27. 3 components 1 bundle still a work in progress will

    land in Symfony 4.3 Symfony Mailer