问题
I'm working on sending emails via SMTP using the Indy components (TIdMessage). Such an email needs to be HTML, and needs to carry attachments.
Now if I send an email as plain text (ContentType := 'text/plain'), and attach a file, the email sends just fine, attachment found and everything.
However, once I change the ContentType to text/html, I have a very bizarre result. The entire body of the email gets replaced with apparently the underlying email data (in my words), and shows the attachment in the body as Base64 data.
For example, here's just the first few lines of such a resulting email:
This is a multi-part message in MIME format --YGuFowdSjNaa=_khosBzZl5L8uGVtfasBX Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline This is the body of a test email. --YGuFowdSjNaa=_khosBzZl5L8uGVtfasBX Content-Type: application/octet-stream; name="TagLogo.jpg" Content-Transfer-Encoding: base64 Content-Disposition: inline; filename="TagLogo.jpg" /9j/4AAQSkZJRgABAQEASwBLAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcU FhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgo KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCADhAG8DASIA AhEBAxEB/8QAHAAAAgMBAQEBAAAAAAAAAAAAAAcFBggEAgED/8QAPxAAAQIFAwEEBwYFAwQDAAAA AQIDAAQFBhEHEiExCBNBdSI3OFFhsrMUMkJxgZEVIzNSgmJyoRaSosEk0eH/xAAWAQEBAQAA
Although the original body was only
This is the body of a test email.
The code is very simple, matching all the examples I can find online...
IdMessage.Charset := 'UTF-8';
IdMessage.ContentType := 'text/html'; // <-- text/plain works fine.....
IdMessage.Body := 'This is the body of a test email.';
... (Assigning Other Unrelated Properties) ...
A := TIdAttachmentFile.Create(IdMessage.MessageParts, 'C:\SomeFile.jpg'); // <-- Originally the only LOC here
A.ContentTransfer := 'base64'; // <-- Tried with and without
A.ContentType := 'application/octet-stream'; // <-- Tried with and without
//A.ContentType := 'image/jpeg'; // <-- Tried with and without
A.ContentDisposition := 'inline'; // <-- Tried with and without
Why is this resulting in a "garbage" email, and how do I solve it while supporting HTML email body with attachments?
PS - If it makes any difference, the attachments will be images used in-line in the email body in the end.
回答1:
Such an email needs to be HTML, and needs to carry attachments
I have blog articles on Indy's website on this very topic, I suggest you read them:
HTML Messages
New HTML Message Builder class
Why is this resulting in a "garbage" email
In a nutshell, because you are not populating the TIdMessage correctly.
In your particular example, you are putting the HTML into the TIdMessage.Body property, but you are also adding an item to the TIdMessage.MessageParts collection. That combination has some special handling inside of TIdMessageClient (which TIdSMTP derives from). In particular, when:
- the
TIdMessageis to be MIME-encoded, - and
TIdMessage.IsMsgSinglePartMimeis false, - and
TIdMessage.IsBodyEmpty()returns false (whenTIdMessage.Bodycontains non-whitespace text), - and
TIdMessage.ConvertPreambleis true (which it is by default), - and the
TIdMessage.MessagePartscollection is not empty, and has noTIdTextobjects in it.
Then TIdMessageClient.SendBody() will move the HTML to a new TIdText object in the MessageParts collection, and generate a MIME-encoded email body with the TIdMessage.Body text as the first MIME part. The assumption is that in this combination, the user has put the message plain-text in the TIdMessage.Body by accident, so Indy moves it where it needs to be for further processing, without changing anything else in the email. However, that means the TIdMessage.ContentType property is not adjusted (probably a bug that should be looked into). In your case, you are setting the ContentType to text/html when it really needs to be a MIME multipart/... type instead (depending on the relationship of the attachments to the HTML).
So, you are effectively sending an email like this:
Content-Type: text/html; charset=us-ascii; boundary="AduWRpEOzrMvJDhg6Jp8EsEFw5Qr1p=_1v"
MIME-Version: 1.0
Date: Tue, 8 Aug 2017 12:58:00 -0700
This is a multi-part message in MIME format
--AduWRpEOzrMvJDhg6Jp8EsEFw5Qr1p=_1v
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
This is the body of a test email.
--AduWRpEOzrMvJDhg6Jp8EsEFw5Qr1p=_1v
Content-Type: application/octet-stream;
name="SomeFile.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
filename="SomeFile.jpg"
<base64 data here>
--AduWRpEOzrMvJDhg6Jp8EsEFw5Qr1p=_1v--
Which tells the receiver of the email that the entire email is HTML when it really is not. It is actually a mix of multiple MIME types working together. Since you want the image to be inside the HTML, the top-level Content-Type header needed to be multipart/related instead (I go into more detail in my blog articles explaining why that is).
how do I solve it while supporting HTML email body with attachments?
If it makes any difference, the attachments will be images used in-line in the email body in the end.
When dealing with TIdMessage, it is best to either:
populate only the
TIdMessage.Bodyby itself, and ignore theTIdMessage.MessagePartscollection.put everything in the
TIdMessage.MessagePartscollection, text and all, and ignore theTIdMessage.Bodyproperty.
In this case, since you need attachments, you need to use the MessageParts collection, so you can either:
put the HTML in a
TIdTextobject within theTIdMessage.MessagePartscollection yourself (don't letTIdMessageClientdo it for you), and then set theTIdMessage.ContentTypetomultipart/related:IdMessage.ContentType := 'multipart/related; type="text/html"'; ... (Assigning Other Unrelated Properties) ... T := TIdText.Create(IdMessage.MessageParts, nil); T.ContentType := 'text/html'; T.Charset := 'utf-8'; T.Body.Text := '<html><body>This is the body of a test email.<p><img src="cid:myimage"></body></html>'; A := TIdAttachmentFile.Create(IdMessage.MessageParts, 'C:\SomeFile.jpg'); A.ContentTransfer := 'base64'; A.ContentType := 'image/jpeg'; A.ContentDisposition := 'inline'; A.ContentID := 'myimage';Or, if you want to provide a plain-text message for non-HTML readers, you need to wrap the HTML and plain-text (in that order) inside of
multipart/alternativeinstead, while keeping the HTML and image attachment inside ofmultipart/related:IdMessage.ContentType := 'multipart/alternative'; ... (Assigning Other Unrelated Properties) ... T := TIdText.Create(IdMessage.MessageParts, nil); T.ContentType := 'multipart/related; type="text/html"'; T := TIdText.Create(IdMessage.MessageParts, nil); T.ContentType := 'text/html'; T.Charset := 'utf-8'; T.Body.Text := '<html><body>This is the body of a test email.<p><img src="cid:myimage"></body></html>'; T.ParentPart := 0; A := TIdAttachmentFile.Create(IdMessage.MessageParts, 'C:\SomeFile.jpg'); A.ContentTransfer := 'base64'; A.ContentType := 'image/jpeg'; A.ContentDisposition := 'inline'; A.ContentID := 'myimage'; A.ParentPart := 0; T := TIdText.Create(IdMessage.MessageParts, nil); T.ContentType := 'text/plain'; T.Charset := 'utf-8'; T.Body.Text := 'This is the body of a test email.';use the
TIdMessageBuilderHtmlclass, and let it configure theTIdMessagecontent for you:uses ..., IdMessageBuilder; MB := TIdMessageBuilderHtml.Create; try // optional... MB.PlainText.Text := 'This is the body of a test email.'; MB.PlainTextCharSet := 'utf-8'; MB.Html.Text := '<html><body>This is the body of a test email.<p><img src="cid:myimage"></body></html>'; MB.HtmlCharSet := 'utf-8'; MB.HtmlFiles.Add('C:\SomeFile.jpg', 'myimage'); MB.FillMessage(IdMessage); finally MB.Free; end; ... (Assigning Other Unrelated Properties) ...
回答2:
The content-type should be multipart/mixed.
来源:https://stackoverflow.com/questions/45575045/tidmessage-attachments-show-up-in-body-as-base64