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 Slide

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

    View Slide

  3. Symfony Mime

    View 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 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 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 Slide

  7. Why is it so different

    from Swiftmailer?

    View 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 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 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 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 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 Slide

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

    View Slide

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

    View 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 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 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 Slide

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

    View 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 Slide


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




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

    View Slide

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

    Welcome {{ email.toName }}!




    Native integration with Twig

    View Slide

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

    Welcome {{ email.toName }}!




    Native integration with Twig

    View 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 Slide

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

    View 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 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 Slide

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

    View Slide

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

    View 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 Slide

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

    Welcome {{ email.toName }}!

    {% endfilter %}
    Inlining CSS

    View 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 Slide

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

    View 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 Slide

  34. What about sending

    our emails now?

    View Slide

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

    View Slide

  36. Symfony Mailer

    View Slide

  37. For transactional emails

    View Slide

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

    or not at all

    View Slide

  39. Email
    Transports

    View Slide

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

    View Slide

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

    SMTP: Shortcuts

    View Slide

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

    HTTP: Raw message

    View Slide

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

    HTTP: Via API

    View Slide

  44. Transport::fromDsn('...');
    smtp://user:[email protected]
    smtp://[email protected]
    smtp://null
    smtp://user:[email protected]
    http://key:[email protected]
    api://[email protected]
    Use a DSN

    View Slide

  45. Transport::fromDsn('...');
    Failover
    api://[email protected] || smtp://[email protected]
    RoundRobin
    api://[email protected] && smtp://[email protected]
    Failover and RoundRobin transports

    View Slide

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

    View 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 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 Slide

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

    View Slide

  50. Go async?

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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());
    }
    }
    public="false">



    Here is the magic that makes it work!

    View Slide

  59. Message is a data object

    View Slide

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

    View Slide

  61. Digression…
    The Messenger Component
    ❤ ❤ ❤

    View Slide

  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

    View Slide

  63. Thank you! ❤

    View Slide