Symfony Mime / Mailer

Symfony Mime / Mailer

Introducing the new Symfony Mime and Symfony Mailer components.

9a22d09f92d50fa3d2a16766d0ba52f8?s=128

Fabien Potencier

March 28, 2019
Tweet

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! ❤