TemplatedEmail API

This discussion is about a new feature which makes it easier to generate styled emails and to have identical email layouts through the whole system.

Current state of sending an email looks like this

$mailMessage = GeneralUtility::makeInstance(MailMessage::class);
$mailMessage
    ->addTo($recipient)
    ->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
    ->setSubject($this->getEmailSubject())
    ->setBody('<html><body>html test content</body></html>', 'text/html')
    ->addPart('plain test content', 'text/plain')
    ->send();

The hard part is to generate a nice HTML and plain content. Especially if creating a lot of styled mails, people are using Fluid’s StandaloneView to generate the content. Everyone is creating his own API which is tedious and should be changed

Example

The new API extends \TYPO3\CMS\Core\Mail\MailMessage by various methods which could look like this

$variables = ['title' => 'My title'];
$templatedEmail = GeneralUtility::makeInstance(TemplatedEmail::class);
$templatedEmail->addTo('reciepient@example.org')
    ->addFrom('noreply@fo.com', 'Test')
    ->setSubject('A mail')
    ->addContentAsFluidTemplateFile('EXT:fo/<path-to-file>.html', $variables, TemplatedEmail::FORMAT_HTML)
    ->send();

A proof of concept extension implementing this can be found at github.com/georgringer/templatedMail

Global Configuration

The global configuration is done by the site configuration

templatedEmail:
  templateRootPath: EXT:templatedmail/Resources/Private/Templates/
  partialRootPath: EXT:templatedmail/Resources/Private/Partials/
  layoutRootPath: EXT:templatedmail/Resources/Private/Layouts/

Every extension can of course override the paths during usage of TemplatedEmail by using $templatedEmail->setTemplateRootPaths(['<path>']);

Translated mails

If emails need to be sent in various languages, there are basically the following options:

Translated strings

If the amount of text which needs to be translated is not much, the ViewHelper <f translate language="fr" /> can be used.

Different templates per language

Instead of translating only parts of a mail, it is also possible to use a different template file per language. This could be done by using

->addContentAsFluidTemplateFile('pathToFile_Fr>.html')

By using partials for non-translatable text (e.g. technical data) there would be no need to have duplicated content in various files.

The API could support developers by providing:

  • a method to set the available language which would be injected as variable into the fluid templates
  • use the language information to find the proper template. E.g. providing fr and the if the template with suffix _fr is available, that would be used.

Impact

  • Fluid’s StandaloneView is provided by the core, so developers need less code themselves
  • All templates can share the same layouts which makes it possible to style all mails by changing 1 Layout file, no matter if it is an email sent by the core (e.g. Install Tool) or by a 3rd party extension (as soon as those are adopted to the new API)

Possible Migrations

Instead of using ->setBody by MailMessage, the new API can be used.

Remarks and notes

Currently not part of this discussion

Organizational

Topic Initiator: Georg Ringer

I like the idea!

Related issue: https://forge.typo3.org/issues/51137
(maybe also https://forge.typo3.org/issues/85134 )

Good idea to have a consistent API. Sharing a layout is also nice.

How would you solve translation of emails?
Example usecase:
User does some action on the french site, so he should get french email.

I think using xlf for translating emails is not usefulf. In my projects I solved it in a way that every language had separate template file for email (language code was part of the template path with fallback to English).

Year, translation is a important topic.

It should be possible to „inject“ a specific language, to enable translated emails Independent of the feusers language, which is mosttly not available when sending out a mail by the system.

It should also be possible to add one ore more css files.

Some hooks/signalslots to transform this css to inline styles (if needed) before sending out, would be also nice.

Same for html to plain text version.

Great idea, this would make the work for ext developer much easier

Thanks for your post. I added a section about how translation could work.

See the header “Currently not part of this discussion” but yes both, CSS + generating plain mail content from HTML would be nice but IMO not part of the 1st implementation

Basically this functionality does not have to be core because it is well solvable with an extension. Probably most of us have hacked together something. Ours includes inlining images with CID-URIs by a ViewHelper.

Anyway it would be a nice addition to the TYPO3 feature set IMHO.

Making it possible to set a language context for StandAloneView in general would be great for many applications. (Excuse me if this is already possible, I might have missed it)

So I guess the question is if the core team wants to support this as a sysext.

I like the idea and think, that localization would be one of the hardest parts to resolve for the TemplateEmail API.

One current problem using <f:translate> in fluid standalone views is the localization cache. As soon as the localization cache is built up, it is not easily possible to set the language <f:translate> will use (especially when rendering multiple standalone views in different languages within the same request). I did some research some time ago and came up with this solution (https://www.derhansen.de/2018/02/multiple-localized-fluid-standalone-views-in-one-request.html),
which resets the localization cache before changing to a different language.

I like the idea of having an easy way to use Fluid in emails. But I don’t think that it is a good approach to replace the setBody() method with any Fluid specific code.

I’d approach the problem from another angle. Create a Fluid templating service built around the standalone view. The configuration could also be in the site configuration, but you could set multiple roots:

fluidTemplates:
  myMails:
    templateRootPath: EXT:templatedmail/Resources/Private/Templates/
    partialRootPath: EXT:templatedmail/Resources/Private/Partials/
    layoutRootPath: EXT:templatedmail/Resources/Private/Layouts/

To use it, you create an instance and call the render method like this:

$tplSrv = GeneralUtility::makeInstance(FluidTemplateService::class);
$mailMessage = GeneralUtility::makeInstance(MailMessage::class)
    ->addTo($recipient)
    ->addFrom($this->getSenderEmailAddress(), $this->getSenderEmailName())
    ->setSubject($this->getEmailSubject())
    ->setBody($tplSrv->render('myMails', 'ThankYou.html', $variables))
    ->addPart('plain test content', 'text/plain')
    ->send();

The first argument is the key/name of the root (see above), the next one is the template file. If not an absolute path or prefixed with EXT: is is relative to the configured root path.

To tackle the language issue the options are to add a language parameter to the render method or the constructor of the service class. I prefer the latter. Passing it in once and making it immutable will make caching easier.

the idea is nice, need to think about the details. I somehow dislike the mixture of functionality. Sending the mail and creating the content with a template engine …

Do you answer to the original proposal by Georg or mine? My proposal doesn’t mix the concerns. The mail code is left untouched, but a new site-wide template rendering service is established.

I meant:

But like the idea of a general template service.

@kaystrobach & @masi

I got the ideas about the new API from new features of Symfony. Please checkout https://speakerdeck.com/fabpot/2-new-symfony-components-httpclient-and-mime?slide=25 and slides beginning with number 42.

Eventually this component will be added to TYPO3 as well and will replace SwiftMailer

Looks good, get the point now. It’s definitely better than swiftmailer as the api is cleaner.

Would be ok to have the htmlMessage and the FluidTemplatedMessage. I think it’s a matter of naming here. Just add the template engine to the object name to avoid confusion

Still see no reason why I need a special variant of the mail service, but well …

But looking at the slides I see a small difference in Symfony’s approach and yours. On slide 42 the Twig template is added as “usual” with htmlTemplate() and the “variables” are passed into it with a call to context().

That’s a bit mor elegant and as theawkward method name addContentAsFluidTemplateFile(). Should have been more like setBodyFromFluidTemplate() to match setBody() of SwiftMailer.

Other than that I’m with Kay, call the class FluidTemplatedMessage. Add the methods textTemplate and htmlTemplate just like Symfony and use the method context() to pass in the variables. Using the sames names as in Symfony will add to the appeal for anyone familiar with that framework.

Maybe add some more to allow for different Fluid configurations:

  • set language key for translation handling
  • use a named per site configuration or override root paths
  • pass in a settings array

This topic was automatically closed after 14 days. New replies are no longer allowed.