Slide 1

Slide 1 text

Back to the basics... Fabien Potencier @fabpot

Slide 2

Slide 2 text

Symfony MIME Everything you need to create beautiful emails Fabien Potencier @fabpot

Slide 3

Slide 3 text

Symfony Mime

Slide 4

Slide 4 text

use Symfony\Component\Mime\Email; $email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text('Some text message') ->html('Some HTML message') ->attach('doc.txt') ; The basics

Slide 5

Slide 5 text

$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

Slide 6

Slide 6 text

$email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text('Some text message') ->html('Some HTML message') ; echo strlen(serialize($email)); Email is a data object Only 2k vs 16k for Swiftmailer

Slide 7

Slide 7 text

Why is it so different
 from Swiftmailer?

Slide 8

Slide 8 text

$email = (new Email()) ->text('Some text message') ->html('Some HTML message') ; $email = (new \Swift_Message()) ->setBody('Some text message') ->addPart('Some HTML message', 'text/html') ; A better data object model 16k serialized 38 objects complex serialization "fixed" headers 2k serialized 7 objects simple serialization "dynamic" headers

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

$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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

$txt = new TextPart('Some content'); $html = new TextPart('Some content', 'html'); $body = new AlternativePart($txt, $html); $email = new Message($headers, $body); Get creative

Slide 13

Slide 13 text

A "complete" email multipart/mixed | |------------> multipart/related | | | |------------> multipart/alternative | | | | | ------------> text/plain | | | | | ------------> text/html | | | ------------> image/png | ------------> application/pdf ->text() ->html() ->embed() ->attach()

Slide 14

Slide 14 text

$email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->setBody($body) ; Mix several approaches

Slide 15

Slide 15 text

$email ->attach('Some content', 'doc.txt', 'text/plain') ->attachFromPath('/path/to/doc.txt') ->attach(fopen('doc.txt', 'r'), 'doc.txt', 'text/plain') ; Attachments

Slide 16

Slide 16 text

$email = (new Email()) ->from('fabien@symfony.com') ->to('fabien@sensiolabs.com') ->subject('Some subject') ->text('Some text') ->html('The new logo: ') ->embedFromPath('logo-small.jpg', 'logo.jpg') ; Embeds

Slide 17

Slide 17 text

// 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

Slide 18

Slide 18 text

Creating Emails with Twig Twig is perfect for emails aka, Twig is not dead and still very useful :)

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Welcome {{ email.toName }} from {{ city }}!

Native integration with Twig Symfony\Bridge\Twig\Mime\ WrappedTemplatedEmail Twig template name Template context

Slide 21

Slide 21 text

{% do email.attach('@docs/doc.pdf') %}

Welcome {{ email.toName }}!

Native integration with Twig

Slide 22

Slide 22 text

{% do email.priority(5) %}

Welcome {{ email.toName }}!

Native integration with Twig

Slide 23

Slide 23 text

$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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

{% block config %} {% do email.attach('@images/planfaidherbe.jpeg') %} {% endblock %} {% block subject %}Email Subject{% endblock %} {% block text %} Optional text representation {% endblock %} {% block html %}

Welcome {{ email.toName }}!

{% endblock %} Native integration with Twig

Slide 26

Slide 26 text

{% 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 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 > 1? --_=_symfony_1551352216_9df84f01f42af586d65b48578466206f_=_--

Slide 27

Slide 27 text

Tip! If the Text part is empty, the BodyRenderer automatically generates one

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

use Twig\CssInliner\CssInlinerExtension; $twig->addExtension(new CssInlinerExtension()); {% filter inline_css %} b { color: red }

Welcome {{ email.toName }}!

{% endfilter %} Inlining CSS

Slide 30

Slide 30 text

{% filter inline_css("@css/email.css") %} b { color: red }

Welcome {{ email.toName }}!

{% endfilter %} Inlining CSS

Slide 31

Slide 31 text

use Twig\Markdown\MarkdownExtension; $twig->addExtension(new MarkdownExtension());

{% filter markdown %} | Version | LTS? | Latest | | ------------- |:-------------:| -------:| | 1.0 | Yes | 1.0.1 | | 2.1 | No | 2.1.33 | {% endfilter %}

Use Markdown to simplify your templates

Slide 32

Slide 32 text

use Twig\Inky\InkyExtension; $twig->addExtension(new InkyExtension()); Use Inky to simplify your HTML https://foundation.zurb.com/emails.html

Slide 33

Slide 33 text

{% filter inky|inline_css(source("@zurb/stylesheets/main.css")) %}

Symfony Connect

Forgot Your Password?

It happens. Click the link below to reset it.

Reset Password

unsubscribe here.

{% endfilter %} Use Inky to simplify your HTML

Slide 34

Slide 34 text

What about sending
 our emails now?

Slide 35

Slide 35 text

Symfony Mailer Everything you need to send emails Fabien Potencier @fabpot

Slide 36

Slide 36 text

Symfony Mailer

Slide 37

Slide 37 text

For transactional emails

Slide 38

Slide 38 text

$transport = new SmtpTransport('localhost'); $transport->send($email); The basics Email is sent for everybody or not at all

Slide 39

Slide 39 text

Email Transports

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

new GmailTransport('user', 'pass'); new MailgunTransport('user', 'pass'); new SendgridTransport('key'); new PostmarkTransport('id'); … SMTP: Shortcuts

Slide 42

Slide 42 text

new MailgunTransport('user', 'pass'); … HTTP: Raw message

Slide 43

Slide 43 text

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

Slide 44

Slide 44 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 45

Slide 45 text

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

Slide 46

Slide 46 text

Popular Providers Built-in Support Amazon SES Google Gmail Mandrill Mailgun Postmark Sendgrid

Slide 47

Slide 47 text

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

Slide 48

Slide 48 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 49

Slide 49 text

public function email(TransportInterface $transport) { $email = (new Email())->…; $transport->send($email); return new Response('Sent'); } Sending emails

Slide 50

Slide 50 text

Go async?

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

$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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

$envelope = new SmtpEnvelope( new Address('sender@example.com'), [new Address('recipient@example.com')] ); SmtpEnvelope

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Message is a data object

Slide 60

Slide 60 text

* SentMessage instances * Only one event: MessageEvent * Built-in listeners: EnvelopeListener / MessageListener * Throttling / SMTP keep-alive More...

Slide 61

Slide 61 text

Digression… The Messenger Component ❤ ❤ ❤

Slide 62

Slide 62 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 63

Slide 63 text

Thank you! ❤