$30 off During Our Annual Pro Sale. View Details »

Symfony Mime / Mailer

Symfony Mime / Mailer

Introducing the new Symfony Mime and Symfony Mailer components.

Fabien Potencier

March 28, 2019
Tweet

More Decks by Fabien Potencier

Other Decks in Programming

Transcript

  1. Back to the basics... Fabien Potencier @fabpot

  2. Symfony MIME Everything you need to create beautiful emails Fabien

    Potencier @fabpot
  3. Symfony Mime

  4. use Symfony\Component\Mime\Email; $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject')

    ->text('Some text message') ->html('<b>Some HTML message</b>') ->attach('doc.txt') ; The basics
  5. $email = (new Email()) ->from('fabien@symfony.com') ->to(new Address('fabien@sensiolabs.com')) ->addTo(new NamedAddress('fabien@sensiolabs.com', 'Fabien'))

    ->addCc('thomas@symfony.com', 'lucas@symfony.com') ->addCc(...['thomas@symfony.com', 'lucas@symfony.com']) ->subject('Some subject') ->text('Some text') ; from, to, cc, bcc
  6. $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text('Some text

    message') ->html('<b>Some HTML message</b>') ; echo strlen(serialize($email)); Email is a data object Only 2k vs 16k for Swiftmailer
  7. Why is it so different
 from Swiftmailer?

  8. $email = (new Email()) ->text('Some text message') ->html('<b>Some HTML message</b>')

    ; $email = (new \Swift_Message()) ->setBody('Some text message') ->addPart('<b>Some HTML message</b>', 'text/html') ; A better data object model 16k serialized 38 objects complex serialization "fixed" headers 2k serialized 7 objects simple serialization "dynamic" headers
  9. echo $email->toString(); sleep(2); $email->to('helene@symfony.com'); echo $email->toString(); Fixed vs dynamic headers

    Different
 set of headers Date, Boundary, Message-ID
  10. $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text(fopen('email.txt', 'r'))

    ->html(fopen('email.html', 'r')) ; echo $email->toString(); foreach ($email->toIterable() as $chunk) { echo $chunk; } String or resources, your choice
  11. // Email extends Message use Symfony\Component\Mime\Message; use Symfony\Component\Mime\Part\TextPart; use Symfony\Component\Mime\Header\Headers;

    $body = new TextPart('Some content'); $headers = (new Headers()) ->addMailboxListHeader('From', ['fabien@symfony.com']) ->addMailboxListHeader('To', ['fabien@sensiolabs.com']) ->AddTextHeader('Subject', 'Some subject') ; $email = new Message($headers, $body); And you get full control!
  12. $txt = new TextPart('Some content'); $html = new TextPart('<b>Some content</b>',

    'html'); $body = new AlternativePart($txt, $html); $email = new Message($headers, $body); Get creative
  13. A "complete" email multipart/mixed | |------------> multipart/related | | |

    |------------> multipart/alternative | | | | | ------------> text/plain | | | | | ------------> text/html | | | ------------> image/png | ------------> application/pdf ->text() ->html() ->embed() ->attach()
  14. $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->setBody($body) ;

    Mix several approaches
  15. $email ->attach('Some content', 'doc.txt', 'text/plain') ->attachFromPath('/path/to/doc.txt') ->attach(fopen('doc.txt', 'r'), 'doc.txt', 'text/plain')

    ; Attachments
  16. $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text('Some text')

    ->html('<b>The new logo: <img src="cid:logo.jpg"></b>') ->embedFromPath('logo-small.jpg', 'logo.jpg') ; Embeds
  17. // Email extends Message extends RawMessage use Symfony\Component\Mime\RawMessage; $message =

    new RawMessage($email->toString()); $message->toString(); Go raw! Serialize an email as a string instead of a PHP object
  18. Creating Emails with Twig Twig is perfect for emails aka,

    Twig is not dead and still very useful :)
  19. use Symfony\Bridge\Twig\Mime\BodyRenderer; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Twig\Environment; use Twig\Loader\FilesystemLoader; use Symfony\Component\Mime\NamedAddress;

    $twig = new Environment($loader = new FilesystemLoader(__DIR__.'/templates')); $loader->addPath(__DIR__.'/images', 'images'); $email = (new TemplatedEmail()) ->from('fabien@symfony.com') ->to(new NamedAddress('fabien@sensiolabs.com', 'Fabien')) ->text('Some text content') ->htmlTemplate('simple.html.twig') ->context([ 'city' => 'Lille' ]) ; $renderer = new BodyRenderer($twig); $renderer->render($email); echo $email->toString(); Native integration with Twig
  20. <p> Welcome <b>{{ email.toName }}</b> from {{ city }}! </p>

    <p> <img src="{{ email.image('@images/photo.jpg') }}"> </p> Native integration with Twig Symfony\Bridge\Twig\Mime\ WrappedTemplatedEmail Twig template name Template context
  21. {% do email.attach('@docs/doc.pdf') %} <p> Welcome <b>{{ email.toName }}</b>! </p>

    <p> <img src="{{ email.image('@images/photo.jpg') }}"> </p> Native integration with Twig
  22. {% do email.priority(5) %} <p> Welcome <b>{{ email.toName }}</b>! </p>

    <p> <img src="{{ email.image('@images/photo.jpg') }}"> </p> Native integration with Twig
  23. $email = (new TemplatedEmail()) ->from('fabien@symfony.com') ->to(new NamedAddress('fabien@sensiolabs.com', 'Fabien')) ->textTemplate('simple.txt.twig') ->htmlTemplate('simple.html.twig')

    ; Native integration with Twig
  24. $email = (new TemplatedEmail()) ->from('fabien@symfony.com') ->to(new NamedAddress('fabien@sensiolabs.com', 'Fabien')) ->template('simple.email.twig') ;

    Native integration with Twig
  25. {% block config %} {% do email.attach('@images/planfaidherbe.jpeg') %} {% endblock

    %} {% block subject %}Email Subject{% endblock %} {% block text %} Optional text representation {% endblock %} {% block html %} <p>Welcome <b>{{ email.toName }}</b>!</p> <p><img src="{{ email.image('@images/photo.jpg') }}"></p> {% endblock %} Native integration with Twig
  26. {% block text %} {% set formula = "2 >

    1?" %} {% autoescape false %} {{ formula }} {% endautoescape %} {% endblock %} {% block html %} {% set formula = "2 > 1?" %} {{ formula }} {% endblock %} Native integration with Twig HTML escape by default with the "name" strategy From: fabien@symfony.com To: Fabien <fabien@sensiolabs.com> MIME-Version: 1.0 Date: Thu, 28 Feb 2019 12:10:16 +0100 Message-ID: <7885775f35b0509444e99290be3216e2@symfony.com> Content-Type: multipart/alternative; boundary="_=_symfony_1551352216_9df84f01f42af586d65b48578466206f_= --_=_symfony_1551352216_9df84f01f42af586d65b48578466206f_=_ Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable =20 2 > 1? =20 --_=_symfony_1551352216_9df84f01f42af586d65b48578466206f_=_ Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable =20 2 &gt; 1? --_=_symfony_1551352216_9df84f01f42af586d65b48578466206f_=_--
  27. Tip! If the Text part is empty, the BodyRenderer automatically

    generates one
  28. But HTML in emails is hard Compatibility with email clients

    Inline CSS Responsive layout …
  29. use Twig\CssInliner\CssInlinerExtension; $twig->addExtension(new CssInlinerExtension()); {% filter inline_css %} <style> b

    { color: red } </style> <p> Welcome <b>{{ email.toName }}</b>! </p> {% endfilter %} Inlining CSS
  30. {% filter inline_css("@css/email.css") %} <style> b { color: red }

    </style> <p> Welcome <b>{{ email.toName }}</b>! </p> {% endfilter %} Inlining CSS
  31. use Twig\Markdown\MarkdownExtension; $twig->addExtension(new MarkdownExtension()); <p> {% filter markdown %} |

    Version | LTS? | Latest | | ------------- |:-------------:| -------:| | 1.0 | Yes | 1.0.1 | | 2.1 | No | 2.1.33 | {% endfilter %} </p> Use Markdown to simplify your templates
  32. use Twig\Inky\InkyExtension; $twig->addExtension(new InkyExtension()); Use Inky to simplify your HTML

    https://foundation.zurb.com/emails.html
  33. {% filter inky|inline_css(source("@zurb/stylesheets/main.css")) %} <container> <row class="header"> <columns> <spacer size="16"></spacer>

    <h4 class="text-center">Symfony Connect</h4> </columns> </row> <row> <columns> <spacer size="32"></spacer> <center><img width="100px" src="{{ email.image("@images/symfony.png") }}"></center> <spacer size="16"></spacer> <h1 class="text-center">Forgot Your Password?</h1> <spacer size="16"></spacer> <p class="text-center">It happens. Click the link below to reset it.</p> <button class="large expand" href="#">Reset Password</button> <hr/> <p><small><center><a href="#">unsubscribe here</a>.</small></center></p> </columns> </row> </container> {% endfilter %} Use Inky to simplify your HTML
  34. What about sending
 our emails now?

  35. Symfony Mailer Everything you need to send emails Fabien Potencier

    @fabpot
  36. Symfony Mailer

  37. For transactional emails

  38. $transport = new SmtpTransport('localhost'); $transport->send($email); The basics Email is sent

    for everybody or not at all
  39. Email Transports

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

  41. new GmailTransport('user', 'pass'); new MailgunTransport('user', 'pass'); new SendgridTransport('key'); new PostmarkTransport('id');

    … SMTP: Shortcuts
  42. new MailgunTransport('user', 'pass'); … HTTP: Raw message

  43. new MailgunTransport('user', 'pass'); new PostmarkTransport('id'); … HTTP: Via API

  44. 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

  45. Transport::fromDsn('...'); Failover api://id@postmark || smtp://key@sendgrid RoundRobin api://id@postmark && smtp://key@sendgrid Failover

    and RoundRobin transports
  46. Popular Providers Built-in Support Amazon SES Google Gmail Mandrill Mailgun

    Postmark Sendgrid
  47. Unified behaviour across all providers Same events for all transports

    Unified DSNs / one env var MAILER_DSN Easily switch from SMTP in dev to a "real" provider in prod / same API Same message interface and serialization
  48. 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
  49. public function email(TransportInterface $transport) { $email = (new Email())->…; $transport->send($email);

    return new Response('Sent'); } Sending emails
  50. Go async?

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

  52. $mailer = new Mailer($transport); Always sync Sync

  53. $mailer = new Mailer($transport, $bus); Sync if $bus is null

    OR bus not configured Async is $bus is configured for EnvelopedMessage Sync or Async, your choice
  54. public function email(MailerInterface $mailer) { $email = (new Email())->…; $mailer->send($email);

    return new Response('Sent... eventually'); } Sync or Async, your choice
  55. public function email(MailerInterface $mailer) { $email = (new Email())->…; $mailer->send($email,

    new SmtpEnvelope(...)); return new Response('Sent... eventually'); } Sync or Async, your choice
  56. $envelope = new SmtpEnvelope( new Address('sender@example.com'), [new Address('recipient@example.com')] ); SmtpEnvelope

  57. Emails are sent async via AMQP framework: messenger: routing: 'Symfony\Component\Mailer\EnvelopedMessage':

    amqp Emails are sent immediately framework: messenger: routing: #'Symfony\Component\Mailer\EnvelopedMessage': amqp Sync or Async, your choice
  58. class MessageHandler { private $transport; public function __construct(TransportInterface $transport) {

    $this->transport = $transport; } public function __invoke(EnvelopedMessage $message) { $this->transport->send($message->getMessage(), $message->getEnvelope()); } } <service id="mailer.messenger.message_handler" class="Symfony\Component\Mailer\Messenger\MessageHandler" public="false"> <argument type="service" id="mailer.transport" /> <tag name="messenger.message_handler" /> </service> Here is the magic that makes it work!
  59. Message is a data object

  60. * SentMessage instances * Only one event: MessageEvent * Built-in

    listeners: EnvelopeListener / MessageListener * Throttling / SMTP keep-alive More...
  61. Digression… The Messenger Component ❤ ❤ ❤

  62. 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
  63. Thank you! ❤