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

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

    View full-size slide

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

    View full-size slide

  3. Symfony Mime

    View full-size slide

  4. use Symfony\Component\Mime\Email;
    $email = (new Email())
    ->from('[email protected]')
    ->to('[email protected]')
    ->subject('Some subject')
    ->text('Some text message')
    ->html('Some HTML message')
    ->attach('doc.txt')
    ;
    The basics

    View full-size slide

  5. $email = (new Email())
    ->from('[email protected]')
    ->to(new Address('[email protected]'))
    ->addTo(new NamedAddress('[email protected]', 'Fabien'))
    ->addCc('[email protected]', '[email protected]')
    ->addCc(...['[email protected]', '[email protected]'])
    ->subject('Some subject')
    ->text('Some text')
    ;
    from, to, cc, bcc

    View full-size slide

  6. $email = (new Email())
    ->from('[email protected]')
    ->to('[email protected]')
    ->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

    View full-size slide

  7. Why is it so different

    from Swiftmailer?

    View full-size slide

  8. $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

    View full-size slide

  9. echo $email->toString();
    sleep(2);
    $email->to('[email protected]');
    echo $email->toString();
    Fixed vs dynamic headers
    Different

    set of headers
    Date, Boundary, Message-ID

    View full-size slide

  10. $email = (new Email())
    ->from('[email protected]')
    ->to('[email protected]')
    ->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

    View full-size slide

  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', ['[email protected]'])
    ->addMailboxListHeader('To', ['[email protected]'])
    ->AddTextHeader('Subject', 'Some subject')
    ;
    $email = new Message($headers, $body);
    And you get full control!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. $email = (new Email())
    ->from('[email protected]')
    ->to('[email protected]')
    ->subject('Some subject')
    ->setBody($body)
    ;
    Mix several approaches

    View full-size slide

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

    View full-size slide

  16. $email = (new Email())
    ->from('[email protected]')
    ->to('[email protected]')
    ->subject('Some subject')
    ->text('Some text')
    ->html('The new logo: ')
    ->embedFromPath('logo-small.jpg', 'logo.jpg')
    ;
    Embeds

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

  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('[email protected]')
    ->to(new NamedAddress('[email protected]', '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

    View full-size slide


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




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

    View full-size slide

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

    Welcome {{ email.toName }}!




    Native integration with Twig

    View full-size slide

  22. {% do email.priority(5) %}

    Welcome {{ email.toName }}!




    Native integration with Twig

    View full-size slide

  23. $email = (new TemplatedEmail())
    ->from('[email protected]')
    ->to(new NamedAddress('[email protected]', 'Fabien'))
    ->textTemplate('simple.txt.twig')
    ->htmlTemplate('simple.html.twig')
    ;
    Native integration with Twig

    View full-size slide

  24. $email = (new TemplatedEmail())
    ->from('[email protected]')
    ->to(new NamedAddress('[email protected]', 'Fabien'))
    ->template('simple.email.twig')
    ;
    Native integration with Twig

    View full-size slide

  25. {% 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

    View full-size slide

  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: [email protected]
    To: Fabien
    MIME-Version: 1.0
    Date: Thu, 28 Feb 2019 12:10:16 +0100
    Message-ID: <[email protected]>
    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_=_--

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    Welcome {{ email.toName }}!

    {% endfilter %}
    Inlining CSS

    View full-size slide

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

    Welcome {{ email.toName }}!

    {% endfilter %}
    Inlining CSS

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. {% 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

    View full-size slide

  34. What about sending

    our emails now?

    View full-size slide

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

    View full-size slide

  36. Symfony Mailer

    View full-size slide

  37. For transactional emails

    View full-size slide

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

    or not at all

    View full-size slide

  39. Email
    Transports

    View full-size slide

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

    View full-size slide

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

    SMTP: Shortcuts

    View full-size slide

  42. new MailgunTransport('user', 'pass');

    HTTP: Raw message

    View full-size slide

  43. new MailgunTransport('user', 'pass');
    new PostmarkTransport('id');

    HTTP: Via API

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. $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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. $envelope = new SmtpEnvelope(
    new Address('[email protected]'),
    [new Address('[email protected]')]
    );
    SmtpEnvelope

    View full-size slide

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

    View full-size slide

  57. class MessageHandler
    {
    private $transport;
    public function __construct(TransportInterface $transport)
    {
    $this->transport = $transport;
    }
    public function __invoke(EnvelopedMessage $message)
    {
    $this->transport->send($message->getMessage(), $message->getEnvelope());
    }
    }
    public="false">



    Here is the magic that makes it work!

    View full-size slide

  58. Message is a data object

    View full-size slide

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

    View full-size slide

  60. Digression…
    The Messenger Component
    ❤ ❤ ❤

    View full-size slide

  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

    View full-size slide

  62. Thank you! ❤

    View full-size slide