Problem description

Currently the "alternative plain text" (sent in addition to HTML) is quite different from the ordinary "plain text" (if the message is plain text only).

  1. This difference is presumably accidental and not what sites want. It's more professional if all the mails are consistent and sites don't want to have to test two different methods.
  2. The "alternative plain text" includes the template but the "plain text" is missing that important feature.
  3. The "alternative plain text" is generated with Html2Text and the "plain text" with MailFormatHelper::htmlToText. We should use Html2Text in both cases (see reasons in #9).
  4. The footers in the alternative plain text sometimes don't look good, especially if converted from a complex layout such as nested tables.

Proposed resolution

  1. When generating "plain text", use exactly the same code as for "alternative plain text"
  2. Add an is_html variable to the template so that sites can provide different markup for plain text, for example as an alternative to complex HTML footers.

Original report

Currently, html2plain conversion is applied to the entire email, not just the content. Given that especially email footers are quite hard to convert from HTML to plaintext generically, it would be great if this could be set in a separate template file just for the plaintext version.

Required ToDos:

  1. Create a Twig template for plain text messages, analogously to the one already existing for HTML messages.
  2. Change the html2plain functionality so that it's only applied to the email content, not the entire template.

Perhaps it might be necessary to have two layers of templates: one that defines the headers, footers, etc (for the two formats separately), and one that deals with the actual message display (involving various mail parameters, additional Twig styling).

Hope this makes sense!

Comments

PhilippVerpoort created an issue. See original summary.

adamps’s picture

Status: Active » Closed (won't fix)

Thanks for the idea.

Unfortunately I feel this is much too specialist to your specific situation for Swift Mailer core module. It adds a lot of complexity. It would be disruptive to existing templates and non-back-compatible. The fact that you are unsure and put 'perhaps' in your description indicates to me exactly the problem which is that one could continue to invent more and more complexity in this direction but it's not really clear that any of the other 35000 users of the module would find it especially useful.

The automatic plain text is an optional feature. If you want to generate your own then the Swift Mailer module will respect it.

adamps’s picture

Title: Separate Twig Template for Plaintext Version » Alternative plain text should match plain text version
Category: Feature request » Task
Issue summary: View changes
Status: Closed (won't fix) » Active

OK, so I agree with the initial problem you are seeing. I propose a different and much simpler way to resolve it - see issue summary.

What do you think?

geek-merlin’s picture

Title: Alternative plain text should match plain text version » Decide how to do plaintext and header/footer conversion

Thanks both! This is a really interesting problem. And i think i remember that quite some people had pain points with this.

I'm not so decided what's the best solution wrt cost/benefit ratio. Template suggestiona offer big fix-my-custom-needs power, while being quite cheap in the end.

Let's get some feedback on this.

geek-merlin’s picture

> We should decide which one [MailFormatHelper::htmlToText vs Html2Text] is better and use is in both cases.

My gut feeling is the answer will be "YMMV"... So i'm not opposed to make this configurable (maybe in the theme layer?).

adamps’s picture

Yes you are right - good to get some feedback. Looking again I think that this issue has touched upon 3 separate topics.

1. When generating the plain text alternative, the current code will use the HTML template then convert to plain. That seems wrong and instead we should do the same as when generating a plain text mail - whatever we decide that should be. This change is non-BC and we should do it before beta.

2. When generating plain text (either alternative or body) it could be useful to have a template something like swiftmailer-plain.html.twig. You are right this is a good idea. However we can add this later in a fully BC way (the default template would be empty) so there is no rush. I feel it would be clearer to create a separate issue for this.

3. Some sites might like to have a complex system of templates with multiple layers and so on. Well Twig allows that with the include mechanism. This seems like it is a site specific choice and not something to include in the core module.

geek-merlin’s picture

Thanks for elaborating. I fully agree on all points.

geek-merlin’s picture

> MailFormatHelper::htmlToText vs Html2Text

Some research indicates that we want to stick with Html2Text until reasons.

So let's focus on the other questions like themability.

adamps’s picture

Issue summary: View changes

So let's focus on the other questions like themability.

I have raised #3130818: Add a template for plain text mails which would be the ideal place to focus on themability.

In the meantime the points in the IS seem entirely valid so let's use this issue to solve them. Thanks for adding related issues which are useful.

In #2830384: Deprecate MailFormatHelper in favor of html2text/html2text library @Berdir suggests to deprecate MailFormatHelper in core. It has barely been maintained and html2text has 450k installs on packagist.

#2940969: Add setting to choose between Html2Text and MailFormatHelper::htmlToText points out that any change without adding an option would be a BC break. However we are just starting a new branch so it is the ideal time to break BC to clear out some deprecated code.

We already use html2text for the plain text alternative and this is documented in the swiftmailer README.

We only use MailFormatHelper when sending plain-text mails for content that was HTML. That's not a main use case of this module - core can already format plain-text mails. Presumably most sites using this module want to send HTML mails. So perhaps most people won't even notice??

To my mind this all strongly suggests that we should stop using MailFormatHelper and use html2text consistently. html2text has lots of config options so we could add a hook to allow customisation of those.

My gut feeling is the answer will be "YMMV"... So i'm not opposed to make this configurable

Before we add that complexity I would like so see some clear scenarios where this would be required.

adamps’s picture

Status: Active » Needs review
StatusFileSize
new6.14 KB

Status: Needs review » Needs work

The last submitted patch, 10: swiftmailer.plaintext-conversion.3126935-10.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new7.19 KB
new2.21 KB
adamps’s picture

This patch solves problems 1. and 2. in the IS. It doesn't provide any configuration to choose MailFormatHelper but we could add that later in a BC way.

PhilippVerpoort’s picture

Dear both,

apologies that it took me a while to get back to you. Thanks so much to both of you for your time and effort.

My main points:

Regarding point 2: I agree that being consistent with the function used for HTML to plaintext conversion is a good thing. Perhaps it might be okay to break BC regarding the plain-text conversion instead of creating a lot of work and complexity that probably nobody is ever going to care about. As you said, I doubt that many people would use Swiftmailer to create plain-text messages anyway, and for those people who do the difference in output between the two functions probably doesn't matter that much. And if you're planning to start a new branch, then I agree it's the perfect timing to clear out deprecated code. If somebody objects, they can always write their own little patch to replace the Html2Text function with another function of their choosing.

Regarding your resolution of point 1: I'm not sure I agree. You're making the assumption that the swiftmailer.html.twig template file contains nothing that would be worth including when converting to plaintext. However, I think it's rather common to have something in there that would be worth keeping. Specifically, it would be great to be able to display any manually populated message parameters in this TWIG template, and ideally this should carry over to the auto-generated "alternative plain text". E.g. I have something like this in that file:

<p>Dear {{ message.params.recipient }},</p>
{{body}}
<p>Kind regards,<br/>{{ message.params.sender }}</p>

So this bit you want to keep in the html2text conversion, whereas you don't want to include things like logos, divs, boxes/frames around the text, preheaders (the message summary bit displayed in the inbox by some modern email apps), etc.

Having given it another thought (and I feel more confident about it now!), I believe the only clean way of handling this is to have two sets (or layers) of templates. One for the inner mail content (that processes the {{ body }} as well as other message params) plus an outer mail styling (that includes the <html> ... </html> tags and would add preheaders, frames, logos, etc.

I know you mentioned that this added layer of complexity is unnecessary because it can already be accomplished by site builders through the TWIG sub-template functionality (e.g. through the {% include ... %} statement). I would normally completely agree with you, but the only reason why this goes wrong has to do with the HTML2Plain conversion. If it weren't for that, I'd agree, but if you want to resolve this in a clean manner, you need another layer of templates.

I don't think adding these templates would necessarily have to break BC. You can just set the new templates to print back the output of the sub-template.

Let me give an example.

Say a module creates this content:

  <p>Your order #12345 has been processed, and we are now shipping your product. Your delivery will arrive on xxx.</p>

Then a sensible TWIG theming would look like this:

Example swiftmailer.twig.html contains:

<p>Dear {{ user_recipient.given_name }},</p>
{{ body }}
<p>If you have any questions, please contact <a href="mailto:{{ customer_service_email }}">customer service</a>.</p>
<p>You can view all details of your order online <a href="{{ link_to_order }}">here</a>.</p>
<p>Kind regards,<br/>Your friendly team at example.com.</p>

Example swiftmailer-plain.twig.html contains:

Dear {{ user_recipient.given_name }},
  
{{ plain }}
  
If you have any questions, please contact customer service here: {{ customer_service_email }}
You can view all details of your order online here [{{ link_to_order }}].

Kind regards,
Your friendly team at example.com.

The {{ plain }} variable is either the plain-text output of the respective module that created that email, or it's a HTML2Text version of {{ body }}. Now because most site builders are not going to care about writing their own swiftmailer-plain template file for this, they'll probably just want to use a default template like this:

Default swiftmailer-plain.twig.html contains:

{{ plaintext_from_template }}

Where {{ plaintext_from_template }} is now the plaintext conversion of the entire swiftmailer.twig.html template output, not just {{ body }}.

On top of that, you allow all the email styling stuff to go into two separate templates:

Example swiftmailer-footer.twig.html contains:

<html>
<!-- add your logo -->
<!-- add preheader -->
<!-- add loads of divs and tables etc to handle your styling -->
{{ body_content }}
<!-- add your HTML signature -->
<p>This email was created by your friendly site example.com. Please reply to this <a href="mailto:help@example.com">address</a>.</p>
</html>

Example swiftmailer-plain-footer.twig.html contains:

{{ plain_content }}
-- 
This email was created by your friendly site example.com
Please reply to this address: help@example.com

And of course these last two new templates should default to the bare minimum in order to ensure full BC.

I know this looks like an awful lot of changes and new templates, but IMHO this is the only way of dealing with this properly. It gives a lot of flexibility to people who want to do email styling correctly, yet those who just want to keep it simple will be able to stick with just using one template.

I know you could probably argue that all of this stuff is irrelevant and that all of the content creation should happen inside the module (perhaps through a separate template in there), and that all that the Swiftmailer module should ever care about is how to turn the body and plaintext of the message into an HTML or plaintext email, yet my personal experience is that it's a hell of a lot easier to add a few message params through a hook and then do all that in the swiftmailer.twig.html template than add another template myself and completely rewrite the content of an email produced by a module inside a hook.

I think email styling really matters to make D8 more competitive, and so I think finding ways to make the styling both flexible but also work out-of-the-box is important.

Let me know what you think! Also I fully understand if you prefer to move this discussion to another issue.

adamps’s picture

@PhilippVerpoort
Thanks for a detailed explanation.

I do prefer to split into separate issues please. I think it will work much better if we check this issue in first, then that leaves a better structure for considering templates. Please can we continue the discussion about templates in #3130818: Add a template for plain text mails?

From reading the first part of your comment I deduce that you are in support of the patch on this issue - great. If you are willing to do a review or test it then that would be even better.

adamps’s picture

Status: Needs review » Needs work

@PhilippVerpoort Apologies I now realise I was too hasty to reply without properly reading your comment. You have carefully thought things through and explained it very well which is a big help. I admit that when I saw about adding layers of templates I had a strong negative reaction without properly analysing what you said. I am also now coming to believe that my idea to split the templates into a separate issue is wrong because the two matters are closely linked.

So let me try to recap what you are saying and put forward some ideas:

Currently the plain text alternative is generated like this:

  • Convert body parts to HTML
  • Apply template containing HTML
  • Convert to plain text

Whereas a plain text mail is generated like this

  • Convert body parts to plain

This issue aims to use the same code for both cases.

Option 1 I had imagined the we would combine the two with code like this

  • Convert body parts to plain
  • Apply a different template containing plain text

However you have pointed out that there might be significant overlap between the two templates. One contains HTML and the other contains plain text so it's difficult or impossible to share content.

Option 2 We can instead use code like this:

  • Convert body parts to HTML
  • Apply template containing HTML with some modifications
  • Convert to plain text

This is what you are proposing except that I have left things very open as to how the template would be different in the plain text case. I am still very reluctant to try to impose a specific hierarchy of template layers because it seems over-complex and risks being specific to a particular use case.

Here is my alternative idea. We can add a template variable is_plain which is a boolean. If the site wants to have parts of the template that are different between HTML and plain then they can simply use

{% if is_plain %}

What do you think?

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new11.14 KB
new7.38 KB

Status: Needs review » Needs work

The last submitted patch, 17: swiftmailer.plaintext-conversion.3126935-17.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

adamps’s picture

Title: Decide how to do plaintext and header/footer conversion » Improvements to plaintext conversion
Issue summary: View changes
adamps’s picture

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new13.85 KB
new6.75 KB

I tested a bit more and it's still not quite right. In the plain text alternative URLs are converted to links then remain in the output twice, something like this

http://example.com [http://example.com]

Also the line breaks are quite muddled.

Here is a new patch that fixes these. It has a call to an internal function _filter_autop which I can fix but lets just check if it passes tests and people like it.

Status: Needs review » Needs work

The last submitted patch, 21: swiftmailer.plaintext-conversion.3126935-21.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

PhilippVerpoort’s picture

Status: Needs work » Needs review

Wow, I'm impressed! Thanks for putting all your hard work into this. And thanks for reading and re-considering my answer so carefully. I also agree it's better to resolve all these things at once because they're intrinsically linked.

Here is my alternative idea. We can add a template variable is_plain which is a boolean. If the site wants to have parts of the template that are different between HTML and plain then they can simply use

{% if is_plain %}

What do you think?

I actually think this is a good idea! I was wondering, if it were possible to combine this with another approach:

Could we perhaps wrap everything that is supposed to be converted from HTML to plain in a <div> ... </div>? And then only feed the content of that div to the html2text function?

An example for a swiftmailer.html.twig file:

<html>
  <!-- add your logo -->
  <!-- add preheader -->
  <!-- add loads of divs and tables etc to handle your styling -->
  <div class="to-be-converted-to-plain">
    <p>Dear {{ user_recipient.given_name }},</p>
    {{ body }}
    {% if is_plain %}
    If you have any questions, please contact customer service: {{ customer_service_email }}
    {% else %}
    <p>If you have any questions, please contact <a href="mailto:{{ customer_service_email }}">customer service</a>.</p>
    {% endif %}
    <p>You can view all details of your order online <a href="{{ link_to_order }}">here</a>.</p>
    <p>Kind regards,<br/>Your friendly team at example.com.</p>
  </div>
  <!-- add your HTML signature -->
  <p>This email was created by your friendly site example.com. Please reply to this <a href="mailto:help@example.com">address</a>.</p>
</html>
{% if is_plain %}
  <div class="to-be-converted-to-plain">
    <!-- add your plaintext signature -->
  </div>
{% endif %}

So then you've got to make sure you pull out all content inside any <div class="to-be-converted-to-plain"> ... </div> environments. I suppose you could use the DOM extension? (Which I believe is a requirement of Drupal 8?). An example:

// create the DOMDocument object, and load HTML from a string
$dochtml = new DOMDocument();
$dochtml->loadHTML($swiftmailer_html_output);

// gets all <div> tags
$divs_all = $dochtml->getElementsByTagName('div');
$divs_process = array();

// traverse the object with all divs
foreach($divs_all as $div) {
  // if the current div has class="to-be-converted-to-plain", adds it in the $divs_process array
  if($div->getAttribute('class') == 'to-be-converted-to-plain') {
    $divs_process[] = $div->nodeValue;
  }
}

$plain = html2text(implode("\n", $divs_process));

Completely untested but just an idea.

What do you think about this?

Sorry, I haven't had a chance to look at your patches. Perhaps you want to have a think about my proposed change and perhaps try to fix that one fail that your last patch produced? I'll see if I can take a look at your patch and perhaps even get it tested in the meantime.

Concerning this:

In the plain text alternative URLs are converted to links then remain in the output twice, something like this

http://example.com [http://example.com]

To be very honest, I wouldn't worry about that too much. I think that's just something somebody would have to live with if they're relying on automated html to plaintext conversion of an email. The only way to stop this from happening is to stop Drupal from converting links of that type to ones enclosed by <a href="...">...</a> tags (which I'd definitely not recommend for the sake of usability for anyone writing HTML emails). An alternative way would be to check if the href bit inside the <a> tag matches the bit enclosed by that tag, and then remove those <a>...</a> tags. You could do that without too much trouble I presume...

PhilippVerpoort’s picture

Status: Needs review » Needs work

Oops, that was an unintended change of the issue status.

adamps’s picture

Thanks @PhilippVerpoort
I think you can get what you want without the special divs processing. Could you do it like this? The links will be converted to a suitable plain text equivalent automatically so you don't need to use an if test there.

{% if is_html %}
<html>
  <!-- add your logo -->
  <!-- add preheader -->
  <!-- add loads of divs and tables etc to handle your styling -->
{% endif %}
    <p>Dear {{ user_recipient.given_name }},</p>
    {{ body }}
    <p>If you have any questions, please contact <a href="mailto:{{ customer_service_email }}">customer service</a>.</p>
    <p>You can view all details of your order online <a href="{{ link_to_order }}">here</a>.</p>
    <p>Kind regards,<br/>Your friendly team at example.com.</p>
{% if is_html %}
  <!-- add your HTML signature -->
  <p>This email was created by your friendly site example.com. Please reply to this <a href="mailto:help@example.com">address</a>.</p>
</html>
{% else %}
    <!-- add your plaintext signature -->
{% endif %}
The only way to stop this from happening is to stop Drupal from converting links of that type to ones

Actually I think we can have the best of both worlds - try the current patch and you should see that the HTML version has a clickable link but the plain text version doesn't have the link twice.

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new14.06 KB
new1.73 KB

Status: Needs review » Needs work

The last submitted patch, 26: swiftmailer.plaintext-conversion.3126935-26.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

adamps’s picture

Status: Needs work » Needs review
StatusFileSize
new14.09 KB
new526 bytes
PhilippVerpoort’s picture

Your solution makes complete sense, and will work much better than anything I had suggested!

I installed the patch, and it works like a charm! Amazing, thanks!!!

I'll set it to "Needs review", and if one more person could find the time to confirm this is working, then I guess we can consider this reviewed.

geek-merlin’s picture

I did not completely dig everything but this looks amazing!

PhilippVerpoort’s picture

@geek-merlin: Would you mind giving it a quick test so we know it's working and we can get this committed?

Just install the patch and add some {% if is_html %} statements to your swiftmailer TWIG template. If it works for you as well, I think we can consider this done!

The template I'm using looks like this. Perhaps this is a good starting point?

{% if is_html %}
{% include '@my_theme/swiftmailer/swiftmailer-header.html.twig' %}
{% endif %}
{{ body }}
{% if is_html %}
{% include '@my_theme/swiftmailer/swiftmailer-footer.html.twig' %}
{% else %}
{% include '@my_theme/swiftmailer/swiftmailer-plain-footer.html.twig' %}
{% endif %}
adamps’s picture

@PhilippVerpoort Thanks for testing. I think for me if one person has tested, plus me that's enough. We are going to make a Beta and if people find bugs then we can fix them.

I feel that the main priority is to check that the concepts are right. We'd prefer not to make any more non-back-compatible changes after releasing a Beta. If anyone has comments on that then I'd like to hear them. However D9 has been released so we shouldn't wait too long before making a compatible release here. I propose to check in at the end of this week if there is no more feedback.

PhilippVerpoort’s picture

Sure, sounds great! Amazing!

geek-merlin’s picture

> Thanks for testing. I think for me if one person has tested, plus me that's enough.

+1 from me on everything, unfortunately i'm super low on bandwidth these days.

adamps’s picture

Great. One last tweak to remove the call to the internal function _filter_autop().

  • AdamPS committed 9cee476 on 8.x-2.x
    Issue #3126935 by AdamPS, PhilippVerpoort, geek-merlin: Improvements to...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.