How to get the rendered body of a templated email before sending?

3.2k views Asked by At

I want to get the render of an email before to send it.

I created a TemplatedEmail with htmlTemplate and context, it works fine for sending but how get the generated template with context to save it in database ? (customer needs)

I tried the getBody() but seems to work only with text template as I get A message must have a text or an HTML part or attachments.

$email = new TemplatedEmail();
$email->htmlTemplate($htmlTemplate);
$email->from($from)->to(...$to)->subject($subject);
$email->context($context);

dd($email->getBody());

I thought to use the render method but I'm in a service and not sure if it's a good way to store in database.

4

There are 4 answers

0
yivi On BEST ANSWER

Symfony only renders the message when actually sending it, via an Event Listener. The class responsible from doing the rendering is BodyRenderer, from the Twig Bridge.

But nothing stops you from rendering the message yourself.

You have the template and the context variables, so you could simply inject Twig wherever you are doing the sending, render the template to a string and do whatever you need with that.

You could also register your own MessageEvent::class listener, set it with lower priority than the one registered by the Twig Bundle (it uses the default priority) so it's executed after that one, and then you could access the message body since it would have been rendered already. This is (very) slightly more complex, but you'd gain some performance since you wouldn't be rendering the template twice.

Which approach to use would depend on your application, your constraints, etc. But the important bit is to realize on what part of the process you'll find the body actually rendered, or render it yourself if you want it before that.

0
TikTaZ On

For future information it's possible to render the template in a service using the documentation here https://symfony.com/doc/current/templates.html#rendering-a-template-in-services

0
Kamil P. On

Inspired by https://stackoverflow.com/a/58932244/1936720, you can also force rendering using

$renderer = new BodyRenderer($twig);
$renderer->render($email);

This is how it could work:

use Twig\Environment;
use Symfony\Bridge\Twig\Mime\BodyRenderer;
....
public function __construct(
    private readonly Environment            $twig,
)
....
$email = (new TemplatedEmail())->....;
....
$renderer = new BodyRenderer($this->twig);
$renderer->render($email);
....
$email->getHtmlBody()   // won't be empty anymore
0
Tomato On

Here is solution (for logging templated email to DB, but can be easily customized to anything else) using EventListener

use App\Entity\EmailLog;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;

#[AsEventListener(event: MessageEvent::class, method: 'onEmailSent', priority: -1)]
class EmailLogListener
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {}

    public function onEmailSent(MessageEvent $event): void
    {
        $message = $event->getMessage();
        if (!$message instanceof Email) {
            return;
        }

        $log = new EmailLog();
        $log
            ->setSentFrom(array_map(function (Address $address) {
                return $address->getAddress();
            }, $message->getFrom()))
            ->setSentTo(array_map(function (Address $address) {
                return $address->getAddress();
            }, $message->getTo()))
            ->setSubject($message->getSubject())
            ->setText($message->getHtmlBody());

        $this->entityManager->persist($log);
        $this->entityManager->flush();
    }
}