Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHPMailer 6 styling conundrum #1705

Closed
tnbnicer opened this issue Apr 13, 2019 · 52 comments
Closed

PHPMailer 6 styling conundrum #1705

tnbnicer opened this issue Apr 13, 2019 · 52 comments

Comments

@tnbnicer
Copy link

I have a problem with PHPMailer 6.0.7, WAMP, PHP 7.3.3, which I hesitate to call a problem, since it is of my own making, and yet, the code was fine in PHPMailer 5.2.9.

Instead of generating an email with $mail-> "headers", I use preexisting headers that are designed to work in the PHP mail function. Wanting to retain mail() compatibility, I insert the same headers and almost the full MIMEBody. Thus the message adds up to two strings. $headers and $mheaders.

$mail->addCustomHeader($headers);
$mail->addCustomHeader($mheaders);

The Body remains empty (with a space).

$mail->Body = ' ';

In its entirety I send:

$mail->isSMTP();
$mail->Host = 'myhost.biz';
$mail->Username = 'image-gallery@mysite.com';
$mail->Password = 'secret';
$mail->SMTPSecure = 'tls';
$mail->Port = 587;
$mail->addAddress($email,$asciiname);
$mail->Subject = $emailsubject;
$mail->setFrom($senderemail,$sender);
$mail->addReplyTo($senderemail,$sender);
$mail->ReturnPath=$sender;
$mail->XMailer = ' ';
$mail->Body = ' ';
$mail->addCustomHeader('Mime-Version','1.0');
$mail->addCustomHeader($headers);
$mail->addCustomHeader($mheaders);

I'm attaching two text email source files graphically illustrating the different outputs with PHPMailer 5.2.9 and 6.0.7 respectively, one, a well-formed email, the other, unreadable by email clients that fail to display the message, that is both plain-text and html.

529.txt
607.txt

It isn't precisely a bug. Question: Is there a way of fixing it?

Thanks.

@Synchro
Copy link
Member

Synchro commented Apr 13, 2019

Fundamentally, just don't do this. You're going against the way PHPMailer is designed to work, and as you're finding, it will not work out well. The right way is to break out your message into its component body and headers (to do that you may want to use a MIME parser; there's a good one in PEAR), and then pass them into PHPMailer in the way it expects. For headers it's quite straightforward - splitting on line breaks should result in something acceptable to addCustomHeader, however, do not try to build MIME structure using custom headers; it won't work. PHPMailer builds the MIME structure, headers, and body for you, so doing things yourself like adding Mime-Version will result in unexpected behaviour.

@Synchro Synchro closed this as completed Apr 13, 2019
@tnbnicer
Copy link
Author

tnbnicer commented Apr 13, 2019

When you say "break out", it isn't easy to set different ContentTypes and Encodings for plain-text and Html. One size fits all, sort of, by default. My solution was to keep the Body and altBody together including boundaries. You can pass strings along, no problem, but the process is automated. For example:

$messageplainqp = quoted_printable_encode(str_replace("\n", $crlf, (str_replace("\r", "", $messageplain))));

Much more convenient to keep the original string, but PHPMailer wasn't playing nice with the original string. Having it quoted-printable put line breaks in that mangled encrypted download links.

I don't know if I'm explaining it well. Essentially, $mheader already concatenates strings. Inserting each string separately into the body or altbody -- how would you go about that?

I started working with PHPMailer two days ago, I'm still kind of a newbie.

Your reply above however does confirm my suspicions.

@Synchro
Copy link
Member

Synchro commented Apr 13, 2019

You're trying to get a square peg into a round hole... If you're doing your own QP encoding, you're missing the point of PHPMailer :) It takes care of all that for you.

As you've noticed, PHPMailer doesn't allow you to build arbitrary MIME structures, it only has a few presets, and you don't get to control them in such fine detail. The advantage of this is that it keeps it simple, but the downside is a certain lack of flexibility. If you want to control everything, I'd recommend looking at Zend_Mail or SwiftMailer, though be warned that they are much more complex (SwiftMailer has >450 classes vs the 3 in PHPMailer!).

@tnbnicer
Copy link
Author

tnbnicer commented Apr 13, 2019

I preferred several years ago before thinking about SMTP and authentication as I am now, a hands-on approach to formatting emails, since HTML in emails is a mess, stating it mildly. Every client has its own quirky rules. Doubling <p> gaps, ignoring single <br>, using HTML3 tags versus style sheets. To accommodate crazy email clients, coding markup by hand was and is unavoidable. And PHPMailer seemed like a means to add SMTP. It is, but at what cost?

Thinking in the opposite direction, would it not be viable to create a simple class to inject SMTP headers into an email and then send it? Like PHPMailer, but different?

You're right, if I had to do it again, PHPMailer would offer significant shortcuts. :)

(Time to clean up some code now.)

@Synchro
Copy link
Member

Synchro commented Apr 14, 2019

What you described is pretty much what PHPMailer does. Generally the issues with HTML email are nothing to do with MIME structure or headers, it's purely down to what you put in Body, and PHPMailer sends what you give it, untouched (apart from transfer encoding). What is the problem with MIME structure that's causing you to take this approach? How is it different to a standard text + HTML multipart/alternative?

@tnbnicer
Copy link
Author

tnbnicer commented Apr 14, 2019

There is an option to enable an inline logo image and send other file attachments, which made multipart/mixed preferable. AFAICT it does no harm when only Html and plain-text are in the message.

I chose quoted-printable and format=flowed; delsp=yes in part through trial and (less) error and in part because "experts" recommended it in topical posts online.

Base64 for Html, because it was congruous with file attachments. Finally, I didn’t trust clients not to break encoded as quoted-printable. Some of the Html lines are long-ish. Encoding into a Base64 chunk seemed the safest.

So I ended up with mixed encodings. I tried to carry those over into PHPMailer, but saw no way of doing it, conventionally, at which point I started to fool around with the MIME structure. Curious whether PHPMailer 6 -- I was using 5.2.9 -- had settings to leave the Body empty, I downloaded and experimented with it, but the opposite was true. I googled PHPMailer, found a GitHub thread on line breaks, and that led me here to ask this question.

Standard text + HTML multipart/alternative would have done the job, but I wanted to keep my backdoor open, to at times be able to revert to mail(), which meant using the headers syntax anyway.

@Synchro
Copy link
Member

Synchro commented Apr 14, 2019

Everything you describe about the structure is available in PHPMailer. It will happily do text + html with embedded and attached images. It uses the most efficient encoding (which is 8bit if that’s applicable, and works well, falling back to QP if line length is an issue), but you can override that.

It’s normal to use multipart alternative and mixed at the same time, and PHPMailer does that.

PHPMailer doesn’t support format=flowed, but you can’t just declare it in the header, you have to actually do it.

I’d also say that using base64 for html is a great way of ending up in spam filters.

Have you tried doing it “the PHPMailer way”, or are you determined to keep swimming upstream?

@tnbnicer
Copy link
Author

tnbnicer commented Apr 14, 2019

I'm up for a challenge. I tried to override quoted-printable on the Html portion when it was set for the plain-text part, but I couldn't figure out how to use $mail->Encoding more than once. Say I wanted to encode text as quoted-printable and Html as base64 -- documentation and examples are somewhat scanty -- what order should the $mail->Encode instructions be in? One after Body and one after altBody resulted, for me when I tried it, in the latter encoding type always being used both on text and Html.

My former web hosting provider had a bare-basics mail tool that only showed monospace text and no Html. There I found that format=flowed; delsp=yes helped.

If encoding Html in base64 can set off spam filters, and PHPMailer chooses the most efficient encoding, maybe I ought to trust it, even though this would mean re-writing the code as it now is from scratch very likely. Presently the code detects the mime type of file attachments based on the extension and further calculates the file sizes (by opening/reading attached files). A lot of preprocessing of variables that goes into the Body/altBody strings, in short. I believe I would also have to drop an $eol variable in most places in strings, since PHPMailer automatically does those.

It won't be an easy chore.

@Synchro
Copy link
Member

Synchro commented Apr 14, 2019

This really seems to be a problem of your own making. Really, go with the flow.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

You have convinced me to go with the flow. Can I convince you that quoted-printable is less reliable than it should be?

Line endings occassionally play tricks on you in plain-text, because they aren't uniform. I like to include a safeguard against orphaned linefeed characters and/or carriage-returns.

quoted_printable_encode(str_replace("\n", $crlf, (str_replace("\r", "", $messageplain))))

In Html markup EOL characters don't matter, but to show that quoted-printable is not 10000% foolproof, a small glitch:

$eol = PHP_EOL;
quoted_printable_encode(str_replace("\n", $eol, (str_replace("\r", "", $messagehtml))))

Result:

<div><br><span style=3D"font-family:Arial, Helvetica, sans-serif;font-size:=
11pt;line-height:14pt;color:black"><span class=3D"fs1">w <a style=3D"color:=
#007777;text-decoration:underline" href=3D"https://www.anydomain.co.uk">www=
anydomain.co.uk</a>&nbsp;&nbsp; e&nbsp;<a style=3D"color:#007777;text-deco=
ration:underline" href=3D"mailto:image-gallery@anydomain.co.uk">image-galle=
ry@anydomain.co.uk</a><br></span></span><br></div>

At the beginning of the fourth line the dot goes missing.

Before quoted_printable_encode a dot is present.

Before I forget, thank you for convincing me. :)

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

For the line breaks, PHPMailer is very careful to handle line breaks consistently, which is why this method is used all over the place.

The missing dots is not a quoted-printable problem; It's because you're not doing SMTP dot stuffing. PHPMailer deals with that.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

Not that I doubt SMTP dot stuffing is somehow the culprit, except another variant that does not delete the dot at the beginning of the line is when instead of replacing single line end characters with PHP_EOL, CRLF (\r\n) is substituted. The mail server might use UNIX line endings -- I don't really know. On Windows CRLF and PHP_EOL are supposed to be the same.

Anyhow, to my vast relief, line end sanitizing is going out the window, because as you say, PHPMailer handles it. The above example you correctly guessed was generated by my script.

I won't take up any more of your time. Thanks for the replies. I noticed a few parsing improvements in PHPMailer. Umlauts on the recipient line, I always struggled with ...

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

Sorry to open the discussion again, Synchro. Correct me if I'm wrong, when you embed an inline image, the top Content-Type must be multipart/mixed. That's how I learned it, but that doesn't happen for me.

I strongly suspect it to be causing plain-text to be inserted twice. In the text string I specify in AltBody and just below a second time, where the html Body is converted to text as though no plain string were specified. It does qualify as a problem. I volunteer to open a new discussion, or continue here.

Ah, the code:

$mail->isSMTP();
$mail->Host = 'ukm1.siteground.biz';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->Username = 'image-gallery@teanow5pm.co.uk';
$mail->Password = 'secret';
$mail->SMTPSecure = 'tls';
$mail->ReturnPath = $sender;
$mail->addAddress($email,$name);
$mail->setFrom($senderemail,$sender);
$mail->addReplyTo($senderemail,$sender);
//$mail->addBCC($senderemail,$sender);
$mail->Subject = $emailsubject;
$mail->XMailer = ' ';
$mail->CharSet = $charset;
$mail->Encoding = 'quoted-printable';
$mail->isHTML();
if ($EmbedLogo != "") $mail->AddEmbeddedImage($logoimg, $cid_logo, $EmbedLogo, 'base64', $mimeimg, 'inline');
$mail->Body = $messagehtml;
$mail->AltBody = $messageplain;

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

There are several odd things here:

$mail->ReturnPath = $sender;

This doesn't exist. It's against RFC to set a Return-Path header; you're probably looking for the Sender property.

Why set Encoding? 8bit (the default) is generally better; it will switch to QP automatically if its needed.

The only time PHPMailer sets the plain text body is if you call msgHTML(); it doesn't happen otherwise. If you set Body and AltBody, calling msgHTML later will override them. Since you're controlling your own message content, I'd advise you not to call msgHTML.

Can you show me the example MIME structure you're ending up with? As you say, it should be a multipart/mixed containing a multipart/alternative. One reason it may not be is if the call to addEmbeddedImage is failing (so you end up without an attachment) - but you're not checking the return value of that.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

Oh, the logo is attached. I was initially impressed. Then I switched to plain-text view, ... I don't know how I came up with ReturnPath. Sorry about that.

Now I've omitted ReturnPath along with Encoding. There's still no multipart-mixed. No change. I'm attaching the RAW output. Without the image everything looks normal, so I do think it has do with the multipart-mixed header, its absence.

alternative.txt

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

I just remembered why I enabled Encoding. The CharacterSet was always us-ascii, even with non-ascii characters in the email.

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

Ah, I've realised I was wrong about MIME structure; the right structure is what is in there: a multipart/alternative containing a text/plain part and a multipart/related, which in turn contains the text/html part and the image/gif part. It's related because the image is related to the HTML, because it's embedded. If you had used addAttachment instead of addEmbeddedImage you would have ended up with a multipart/mixed at the top level.

It's an RFC contravention to declare an 8-bit charset if your content is 7-bit clean - in the example you provided there are no 8-bit chars, so it automatically downgraded to us-ascii, which is UTF-8 compatible up to 7 bits. Note the has8bitChars function which assesses whether this needs to happen. If you can provide a test case that fails this, I'm all ears...

There is quite a lot of "magic" going in here, but you can see it's all there for a reason!

@tnbnicer
Copy link
Author

Hi. addAttachment has the same behavior. I'm surprised that it embeds the image, since there is no CID parameter. It does embed the image, but the Content-Type multipart/alternative stubbornly remains, as well as the second instance of plain-text (actually converted Html).

Re: the RFC contravention. At present the email contains only ascii, but if the recipient name has 8-bit characters, those will also be part of the main content. The greeting would read: Hi Herr Hühn, ...

Automated emails are unpredictable that way.

I think I'm back on square one. Trying to get a square peg into a round hole...

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

Sure, and if you had included 8-bit text in your example message, it would not have been downgraded to us-ascii - that's why that check is automated.

Are you certain that the second text part is in the sent message, and not being added at the receiving end? It sounds very much like the kind of thing that some mail filters do, and the example you provided does not have a second plain text part.

@tnbnicer
Copy link
Author

Correction: without CID the image of course does not get embedded. It's an attachment. In either case I have the MIME structure: multipart/alternative, multipart/related. At no point multipart/mixed, which I believe it should be, regardless of the attachment's disposition. I could be wrong.

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

Which of PHPMailer's preset MIME structures will be used is evaluated here, and what top-level MIME structure that translates to happens here.

@tnbnicer
Copy link
Author

We posted at the same time. No, I know it's from the Html. The title "publication download links" only appears there. A mail filter would have to be an AI to copy text verbatim.

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

Mail filters often do that - HTML to plain text is pretty easy - PHPMailer 5 used to bundle a converter. I encountered a mail filter at a big corporate site that converted HTML to text by simply stripping tags, and imported and inlined all external images. It made a right mess.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

I don't know. I could try tweaking something in the source code, if you give me time. It's hard to say whether my endeavors will succeed.

I could post what happened in a few hours. In the meantime, fresh ideas are welcome. Thanks.

edit: I'm skeptical about the filter theory. For one, the same filters were in operation when I used the mail function that produced multipart/mixed, multipart/alternative. multipart/related.

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

It's very easy to confirm - check that what you sent is what you receive. You can get a copy of the locally-generated message using getSentMIMEMessage(), BCC it to an account on a different server (e.g. hotmail), or alternatively set SMTPDebug = 2 and watch the message on its way out.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

Just switched back to mail(). No problems there. Here it is in RAW.

inlineraw.txt

edit: The copy wouldn't tell me very much. By looking at the message source alone, I wouldn't be able to see where the additional plain-text came from. All I'd have is the same message.

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

As yet you have not shown me anything that's not behaving as expected. Don't assume; check. The send-side copy would tell you exactly what you want to know, because if it's been altered in transit, what you sent will not be the same as what arrives. Show me PHPMailer sending an unexpected second text/plain part.

Sending via mail() isn't just a difference in mechanism, it's also a difference in route.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

There is no second text/plain part. I vaguely recall that when I was developing the mail() version, I had similar issues, because the Content-Type boundaries were misplaced or missing. It took a lot of figuring out. The appended part is the Html stripped of tags. I won't argue with you about that. But it was not a mail filter that did the stripping. It is normal for a mail client to insert the Html into plain-text whenever the Content-Types are wrong.

To see what it looks like, below is a screenshot. I have to take a break for a while.

doubleplain

@Synchro
Copy link
Member

Synchro commented Apr 15, 2019

Seriously. Post your code, post example debug output, post exactly what you're passing in to Body and AltBody. So far I've seen no evidence that you're actually checking the things that matter, nor the slightest evidence that PHPMailer is doing anything wrong. I've asked to see these things and you seem to be going out of your way to avoid doing so, preferring to second-guess and assume, which is not helping either of us get to the bottom of this.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 15, 2019

SMTP log attached. Do we at least agree that multipart/mixed is mandatory?

smtp.log

Update:
$mail->inlineImageExists() = true;
$mail->attachmentExists() = false;
$mail->alternativeExists() = true;

@Synchro
Copy link
Member

Synchro commented Apr 16, 2019

No, multipart/mixed is not mandatory; it should be used where it is correct to do so - which is not at the top level of a text + HTML message. I'd normally expect to see it on say a plain-text only message with attachments, for example if you do this:

$mail->isHTML(false);
$mail->Body = 'hello';
$mail->addAttachment('somefile.jpg');

In PHPMailer's MIME options, the fact that attachmentExists is false means that it will not use multipart/mixed, because the inlined image is bundled with the text/html part that it's related to - it's not an unattached part of equal status as a mixed designation would suggest.

I extracted the raw message from your SMTP log, and it renders just fine, no stray text parts at all:

image

@tnbnicer
Copy link
Author

Indeed, the Html is fine.

Thanks for your reply and sorry for the delay.

I for my part tried to reconstruct the PHPMailer syntax in mail(), to see if it would also render plain-text twice. It does. So it's the syntax.

I have no experience with PHPMailer earlier than 5.2.9. I'm tempted to infer, probably an evil habit, that at some point the MIME structure syntax changed. Attachments and inline images were treated as mixed content, both. Related makes sense for inline images for the reason you mentioned yesterday: inline images are part of the Html.

In terms of the syntax it was possible also to assign two Content-Types.

Content-Type: multipart/related; type="multipart/alternative";

Maybe the only approach currently available to embed images (better than linking them), seems to be to override the Body and AltBody in a function, msgHtml(), replacing the complete strings. Fortunately this embeds inline images. You can use Html2text that converts the Html email contents to text.

Why go to all the trouble of converting Html to plain-text by stripping out tags? Wouldn't it be much easier to insert the plain-text string again?

$mail->Body = $messagehtml;
$mail->AltBody = $messageplain;
$mail->msgHTML($messagehtml, $basedir = '',true);
$mail->html2text($html, function($html) {return $messageplain});

I realize the syntax is wrong, but if $messageplain is the string I really want, can't I re-use it?

@Synchro
Copy link
Member

Synchro commented Apr 16, 2019

It's really not clear what it is you're trying to do other than making work for yourself! Nowhere have you shown what it is that you're trying to achieve that you can't do currently. You seem to be getting tied up in problems that have been solved - PHPMailer does a good job of handling basic MIME structures like these, and I've not seen any problems relating to that here.

I for my part tried to reconstruct the PHPMailer syntax in mail(), to see if it would also render plain-text twice. It does. So it's the syntax.

What syntax? I've still not seen your actual data you're passing in, other than what I extracted from the SMTP log. I have seen no code that generates this double plain text rendering in PHPMailer.

I don't recall inline images and attachments ever being always treated as mixed, and I've been on this for a while...

Multiple content types on the same MIME part? No, definitely not. It makes no sense to do that; it would just confuse clients or be ignored.

Indeed those extra operations are unnecessary - the point of converting HTML to plain text is in cases where you don't have a plain text version. If you do, then you don't need to use a converter. If you don't want the conversion, reassign AltBody afterwards:

$mail->msgHTML($messagehtml, $basedir = '',true);
$mail->AltBody = $messageplain;

Also bear in mind that msgHTML is a convenience function - you can achieve exactly the same thing manually by using addEmbeddedImage and setting your own cid values.

Do you actually have a clear problem statement that I have a chance of addressing?

@tnbnicer
Copy link
Author

tnbnicer commented Apr 16, 2019

It doesn't work.

$mail->msgHTML($messagehtml, $basedir = '',true);
$mail->AltBody = $messageplain;

I thought I might work to convert Hml to plain-text. On closer examination two plain-texts are being sent yet again.

There is a remote possibility that my PHP is sending the plain-text twice. How at the same time it could not be submitting the Html in the same message twice is beyond me.

I could post the send form online, provisionally only, because it still has that glitch, and point you to the page, or send you PHP code, three pages worth. It could be anything.

What do you advise?

@Synchro
Copy link
Member

Synchro commented Apr 16, 2019

Define "doesn't work". The empty $basedir will mean that images don't get imported, as per the docs.

I recommend doing what I asked - show me a sent message that contains the two text parts. Note a sent message, not a received message - use getSentMIMEMessage() as I suggested, then compare it with the received version of the same message. You still have not established the source of the problem, nor indeed shown the corruption other than in a screen shot of a received message, which doesn't tell me much.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 16, 2019

Sent and received always match, but I'm attaching the output.

The problem boils down to the client or the syntax. Windows Live Mail 2012 is confused by the syntax. I send encrypted download links to customers on my site who purchase digital items securely via PayPal. In 2016 PayPal updated its requirements for sellers. Emails sent should be PCI compliant and TLS1.2, although it isn't exactly said whether the rules apply to encrypted download links.

PHPMailer is worth it. In every way, except for the inline image nuisance (a Windows Live Mail nuisance perhaps). Better than mail() for my limited purposes.

You may disagree with this summary. Please never use Windows Live Mail.

Here's the getSentMIMEMessage() text.

MIME.txt

@Synchro
Copy link
Member

Synchro commented Apr 16, 2019

I looked more into the definition of multipart/related, and found this section. Earlier you said:

Content-Type: multipart/related; type="multipart/alternative";

That is indeed nonsensical (it's futzing with structure, not content), however, the type param does indeed exist, and acts (as the RFC says) as a type hint to allow clients to obtain the primary media type of an embedded part without having to look inside the multipart wrapper, so here we might want it to look like this:

Content-Type: multipart/related; type="text/html";

PHPMailer doesn't set that, and so a client would have to look inside the multipart to figure out what the child type is. This isn't a problem in itself, but this may be where Live's bug is, so let's give it a try. Change this line from this:

$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');

To:

$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . '; type="'. static::CONTENT_TYPE_TEXT_HTML .'"');

See what happens...

@tnbnicer
Copy link
Author

Although it fixed the duplicate issue, adding that had the undesired effect of displaying the standard text in Html view and in plain-text view. Gmail also displayed the text version. No more Html.

@Synchro
Copy link
Member

Synchro commented Apr 16, 2019

Odd. It works perfectly in Apple Mail. I just found this Microsoft doc from 2011 that describes exactly this kind of MIME structure, and Outlook's support for it. What we're doing here is similar to "combo #5", except we don't need the overall /mixed wrapper because there are no attachments, so we're doing exactly what's in part 1.1 of their example structure.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 17, 2019 via email

@tnbnicer
Copy link
Author

tnbnicer commented Apr 17, 2019

What I meant is, if you feel like any more testing …

I would not be averse to a few more experiments, if it were possible to send emails with a combo 5 structure, for instance.

You have been terrifically helpful.

@Synchro
Copy link
Member

Synchro commented Apr 17, 2019

You can get PHPMailer to use a multipart/mixed wrapper (like combo 5) - just add an attachment (not inline / embedded).

I've just pushed changes to add the type property to multipart/related elements, so please check out master to do more experiments on this.

@tnbnicer
Copy link
Author

Off the record I would say that the type parameter after the boundary is more likely to do nothing. Does the RFC specify a particular placement in multipart/related content?

Where you place type before the boundary, MS clients attach the Html and the embedded image as a physical file in text format that you can click and open in an editor.

After the boundary, MS clients apparently disregard type. The email renders normally, however the WLM bug is not fixed.

Adding/omitting it made no difference in how messages displayed in clients on Windows.

@Synchro
Copy link
Member

Synchro commented Apr 22, 2019

I don't think order should be important, but the example in the RFC has boundary before type. I only just noticed that the type param is mandatory in the RFC, so it is important that it's there:

The type parameter must be specified and its value is the MIME media type of the "root" body part.

If windows clients are going to be so buggy, you may be better off cutting your losses and reverting to using an externally referenced image instead, avoiding these problems altogether.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 22, 2019

I did it the hard way. (Keeping my options open.)

[…]
else { // phpmailer

$mail->isSMTP();
$mail->Host = 'ukm1.siteground.biz';
$mail->Port = 587;
$mail->SMTPAuth = true;
$mail->Username = 'image-gallery@teanow5pm.co.uk';
$mail->Password = 'secret';
$mail->SMTPSecure = 'tls';
$mail->Sender = $senderemail;
$mail->addAddress($email,$name);
$mail->setFrom($senderemail,$sender);
$mail->addReplyTo($senderemail,$sender);
//$mail->addBCC($senderemail,$sender);
$mail->Subject = $emailsubject;
$mail->XMailer = ' ';
$mail->CharSet = $charset;
if ($EmbedLogo != "") {
	//$mail->addEmbeddedImage($logoimg, $cid_logo, $EmbedLogo, 'base64', $mimeimg, 'inline');
	$mail->Body = ' ';
	$mail->addCustomHeader('Mime-Version','1.0');
	$mail->addCustomHeader($headers);
	$mail->addCustomHeader($mheaders);
} elseif ($plainonly) {
	$mail->WordWrap = 70;
	$mail->Body = $messageplain;
} else {
	$mail->Body = $messagehtml;
	$mail->AltBody = $messageplain;
}

In PHPMailer.php I disable folding with if this->Body == ' ' to get it to work. It still won't work, if Herr Hühn is the addressee. So there's a switch for utf-8 too.

@tnbnicer
Copy link
Author

tnbnicer commented Apr 22, 2019

To clarify so that others can see, in 6.0.7 the relevant lines are 3151 and 3152, from this:

$encoded = str_replace(static::$LE, "\n", trim($encoded));
$encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded);

To:

if ($this->Body != ' ') {$encoded = str_replace(static::$LE, "\n", trim($encoded));
$encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded);}

Disable folding. The body and headers aren't normally supposed be to replaced. To make room for it, one other change is needed here.

Before:
if (!$this->sign_key_file) {

And after:
if (!$this->sign_key_file && $this->Body != ' ') {

A word of caution might be good. Best practice is to leave PHPMailer.php the way it is and not make these changes. The technical term for it, I think is 'unsupported'; some say 'hack'.

@Synchro
Copy link
Member

Synchro commented Apr 22, 2019

That "fix" strikes me as exactly the kind of thing child classes are for - you shouldn't need to edit any of the original code in order to override bits of it. Anyway, I'm glad you have a solution, and I'm happy we've got an improvement in multipart/related handling, even if that wasn't what you set out to do :)

@tnbnicer
Copy link
Author

Yes. I've grown attached to Windows Live Mail. According to Litmus in 2018, 1% of the market share.

@tnbnicer
Copy link
Author

You should have a semicolon separating the type property if after the boundary.

I wasn't sure at first. After the boundary ... before the boundary ... However, the preceding boundary breaks without a semicolon. Gmail displays the email source, rather than the email itself.

This is more correct:

$body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');

@tnbnicer
Copy link
Author

tnbnicer commented Apr 27, 2019

I wonder also if replacing the tab-space with a space might cause line-length problems in some scenarios.

I haven't experienced any (problems), but … email clients do strange things.

@Synchro
Copy link
Member

Synchro commented Apr 27, 2019

Are you referring to the changes I made for #1714?

Thanks for testing the multiple params - It turns out that omitting semi-colons between multiple params is a mistake in the RFCs! Discussion of it here. I've just pushed a fix for that as you described.

I don't think I've replaced tab-space with space anywhere, only tabs with spaces (i.e. a single char change), so there should be no line-length changes.

@tnbnicer
Copy link
Author

It wasn't so much testing as stumbling across it, and I had a hunch it might be the type property.

I thought you replaced 'line-end'+'tab' with 'space'. Merging lines effectively. But you're right, it is only a tab for a space. Gmail prints it all in one line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants