Generate mails with Twig

Yesterday, I discovered a new way for generating mails with Twig, in a very powerful way.

The problem

It started because of someone explaining me he was using something similar to this:

<?php
// Subject was loaded from a yaml file
$subject = 'Welcome to newsletter, %%customer_name%%';

$values = array(
    '%%customer_name%%' => $customer->getName(),
    '%%customer_mail%%' => $customer->getMail()
    // ...
);

foreach ($values as $name => $value) {
    $subject = str_replace($name, $value, $subject);
}

For generating the content of the mail, it was something like this:

<?php
$twig = $this->get('twig'); // a Twig_Environment instance
$bodyHtml = $twig->render('mail_newsletter.html.twig');
$bodyText = $twig->render('mail_newsletter.text.twig');

The problem with this solution is that you have two different types of templating engine. The first one is a PHP str_replace templating engine and the second one is Twig.

After some searches, I finally discovered a powerful way for generating mails:

The solution

First, you need to create a template, let's assume mail.twig:

{% block subject 'Welcome to newsletter, ' ~ customer.getName() %}

{% block body_html %}
    This is the <strong>HTML body</strong>.
{% endblock %}

{% block body_text %}
    This is the text body.
{% endblock %}

This is perfect: in one file, we have every informations for generating the mail content.

Let's see now how to fetch it from Twig:

<?php
$twig = $this->get('twig'); // Twig_Environment

$template = $twig->loadTemplate('mail.twig');
$parameters  = array(
    'customer' => $customer
);

$subject  = $template->renderBlock('subject',   $parameters);
$bodyHtml = $template->renderBlock('body_html', $parameters);
$bodyText = $template->renderBlock('body_text', $parameters);

And now, you have everything for creating your mail message, created from one file only.

This also means you can use in your template every Twig possibilities, like the translation extension, or the inheritance mechanism of templates.

Integration in your application

Now, we have a raw PHP implementation, let's see how it's possible to integrate it in your application.

The idea is to create a class for preparing the Swift message, so in the end we only need to set the receiver, and that's all!

A mail generator :

<?php
class TwigMailGenerator
{
    protected $twig;

    public function __construct(Twig_Environment $twig)
    {
        $this->twig = $twig;
    }

    public function getMessage($identifier, $parameters = array())
    {
        $template = $this->twig->loadTemplate('mail/'.$identifier.'.twig'); // Define your own schema

        $subject  = $template->renderBlock('subject',   $parameters);
        $bodyHtml = $template->renderBlock('body_html', $parameters);
        $bodyText = $template->renderBlock('body_text', $parameters);

        return Swift_Message::newInstance()
            ->setSubject($subject)
            ->setBody($bodyText, 'text/plain')
            ->addPart($bodyHtml, 'text/html')
        ;
    }
}

For using it, very simple: suppose you have organized your mails according to the schema mail/<identifier>.twig, the code to use it would be:

<?php
$twig = $this->get('twig');     // Twig_Environment
$mailer = $this->get('mailer'); // Swift_Mailer

$generator = new TwigMailGenerator($twig); // Can be in a DIC

$message = $generator->getMessage('newsletter', array(
    'customer' => $customer
));

$message->setTo($customer->getMail());
$mailer->send($message);

That's all for the suggested implementation! No need to create a dedicated bundle for it, or some kind of reusable code. There are 20 lines of code, just integrate it in your application to fit your needs, and it's enough.

Enjoy Twig!

Peter September 30, 2011

This is good, however what would you do when the email text (subject, body) come from the database? Would you generate a 'virtual' twig template and use its placeholder replacement system?

Most of our clients want to edit their emails; currently I have an arcane class-based system which lacks flexibility but validates every message they compose against its allowed placeholders. Can't wait to replace it with something as user-friendly but distinctly better architected :)

Henrik September 30, 2011

This does not allow you to extend and have a parent template like base.html.twig which enables a layout.

Alexandre Salomé September 30, 2011

@Henrik : It works : I bootstraped a quick-test with Silex : https://github.com/alexandresalome/Generate-Mails-With-Twig

Alexandre Salomé September 30, 2011

@Peter : Depends of the functionalities you want to provide. If your customer can accept the Twig templating format, a templating-db-loader will be the solution.

Otherwise, composition could be a solution : the customer fills 3 fields : subject, text, HTML. You only allow him to use {{ var }} syntax (no {% block %}) to avoid confusion. Then your DB loader will assemble the 3 fields.

Elliot Anderson October 1, 2011

You have a trailing semicolon on your return Swift_Message::newInstance(); that might throw some people off.

Julien DIDIER November 14, 2011

Be aware that if you are using Symfony2, Twig doesn't work with templates named "template.twig".
You have to define a pre-extension, like "template.mail.twig" (for this case of mails).
Otherwise Twig will tell you he can't find your template.