silverstripe-framework: email templating broken

Affected Version

4.2.2

Description

None of the email templating works according to the docs… https://docs.silverstripe.org/en/4/developer_guides/email/

  • you can’t override the default email template as described.
  • if you use setBody() or pass in anything as a body, the entire template is replaced by that body, ignoring any setHTMLTemplate directive.
  • there’s no automatic plaintext render if the body is set.
  • the generic template expects something called $EmailContent, so can’t use setBody($body), must use addData(‘EmailContent’,$body)
  • the plaintext render leaves behind artifacts such as line breaks & spacing, and prints the contents of <script> and <style> tags

Steps to Reproduce

Just try the password reset email, it’s unstyled and the plaintext version doesn’t include the link. Or try coding a simple email: Email::create('test1@here.com', 'test2@here.com', 'hello')->setBody('this is <a href="http://google.com">my google</a>')->send(); The results are… inconsistent? Do the docs need updating?

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Comments: 15 (14 by maintainers)

Most upvoted comments

  • if you use setBody() or pass in anything as a body, the entire template is replaced by that body, ignoring any setHTMLTemplate directive.

This is still an issue in 4.4.4. I managed to work around it by changing $email->setBody($body) to:

$email->setData([
    'Body' => DBField::create_field('HTMLFragment', $body),
]);

I encountered some of these problems today. Partly it’s a documentation issue but also the built in templates have problems.

  1. The docs reference a SilverStripe/Email/GenericEmail.ss template which doesn’t exist. There is a SilverStripe/Control/Email.ss which presumably is what replaced this template.
  2. The same section of the docs has a code sample which uses the mail($from, $to, $subject, $body) format, then goes on to talk about the GenericEmail template. Even if that template existed, if you pass a $body argument then that template would not be used, $body is taken as the entire html body (as noted earlier).
  3. The Email.ss template is structured as a wrapper, with a $EmailContent placeholder. Presumably it’s intended that you can pass inner html to this wrapper, so you can work just with content and not worry about the html shell. Indeed the ForgotPasswordEmail.ss and ChangePasswordEmail.ss templates contain only paragraph tags so it looks like the intention is for those to be rendered out and passed in to Email.ss as $EmailContent. This doesn’t happen though. Those emails call e.g. ->setHTMLTemplate('SilverStripe\\Control\\Email\\ChangePasswordEmail') so the result is an html email with no body tag or other boilerplate.
  4. Apart from within the Email.ss template, there are no references to EmailContent anywhere in the SilverStripe Framework/CMS codebase or docs. If you call ->setData() on an Email without specifying a template then that template will be used, but you would have to know by digging in to the code to pass your content in a EmailContent property.

One solution would be to:

  1. Document the existence of the SilverStripe/Control/Email.ss template and tell people they can use it like $email->setData('EmailContent' => DBField::create_field('HTMLFragment', '<p>Some content</p>')
  2. Change the password change/reset email methods to first render and capture the contents of their respective templates, then send it using the Email.ss template.

This should correct the html markup that is going to people’s email clients, and would allow SS developers to provided their own branded SilverStripe/Control/Email.ss template and know that it will be applied to these system emails.

A drawback to that (and possibly making it a breaking change) would be that if a developer has already overridden the password templates and added a html/body shell to it, then you’ll get broken markup - a double shell. It also ultimately gives you less flexibility for modifying those emails since you could only edit the inner content. Perhaps that could be offset by letting developers set a template for these emails through config that could be used in place of Email.ss if they wish.

Maybe an idea for SilverStripe 5 would be to extend the Page.ss + Layout/Page.ss approach to emails? It’s essentially the same problem and developers are used to that solution.

BTW:

there’s no automatic plaintext render if the body is set. The plaintext render (…) prints the contents of <script> and <style> tags

These issues may have been fixed as $email = new Email('noreply@example.com', 'example@example.com', 'test subject', '<!doctype html><html><body><style>style</style><script>script</script><p>test</p></body></html>'); $email->send(); worked fine for me, the plain text component was just test.

Regarding CSS markup appearing in plaintext, you can suppress it by adding HTML comment tags! It doesn’t seem to affect the rendering of the HTML version and the plaintext version dutifully removes the fake comment.

<style><!--
p.unwanted { color: red }
--></style>

My quick and dirty test code

PageController.php:

private static $allowed_actions = [
    'thing'
];

public function thing(HTTPRequest $request)
{
    $member = Security::getCurrentUser();
    $email = Email::create('guy@silverstripe.com', 'guy@silverstripe.com', 'This is a subject');

    $data = $member->customise([]);

    $email->setHTMLTemplate('Includes/email/MemberEmail');
    $email->setData($data);

    $email->send();
}

templates/Includes/email/MemberEmail.ss:

<div>
    <h1>Hi $FirstName</h1>
    <p>Your email is <em>$Email</em></p>
</div>

Reproduces the issue.

However I feel this is a little different to the issue suggested by the OP.