Keycloak Mail Templates: force `Content-Transfer-Encoding: quoted-printable` for text MIME part - email

I'm using Keycloak 15.0.2. When sending an account verification email, the email that gets sent uses Content-Transfer-Encoding: 7bit for the text portion of the email.
This causes the verification link to be on one line, and violates RFC 2822 by having a line that's very long, causing my emails to be bounced.
The HTML portion of the email is properly encoded with Content-Transfer-Encoding: quoted-printable.
I've been trying to look at the source of Keycloak, but my knowledge of java is too poor to really figure it out. I'm sure somewhere the MIME message gets parsed at which point it decides on a header for each part. But I can't find where.
I have seen messages where the text portion did have the correct encoding. So I assume there's a certain condition somewhere that will force the encoding. But I can't find it.
How can I force Keycloak (or Freemarker, or javax MimeBodyPart) to use quoted-printable?
Example of a MIME output:
Content-Type: multipart/alternative; boundary="----=_Part_2_1488711957.1660016366185"
Date: Tue, 9 Aug 2022 03:39:26 +0000 (GMT)
From: Mails#covle.com
MIME-Version: 1.0
Message-ID: <126146379.3.1660016366188#b02efe4baa19>
Received: from b02efe4baa19 by mailhog.example (MailHog)
id duuNy3ONelpvr8ukUqz7WBJnrtPd0oSw43G2W9w8Ix4=#mailhog.example; Tue, 09 Aug 2022 03:39:26 +0000
Reply-To: Mails#example.com
Return-Path: <Mails#examplecom>
Subject: Verify email
To: asdasd#example.com
------=_Part_2_1488711957.1660016366185
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit
Someone has created a Bluppie account with this email address. If this was you, click the link below to verify your email address
http://localhost:8080/auth/realms/bluppie/login-actions/action-token?key=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzODYxY2JmMy0wMWYzLTRhMmQtOTg1NC02MmEyYWMyYzhjNzUifQ.eyJleHAiOjE2NjAwMTY2NjYsImlhdCI6MTY2MDAxNjM2NiwianRpIjoiZDVlYjlhODMtMDE0NS00YTBhLTk2M2YtYjBkMjI0ZTA0ZWVkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2JsdXBwaWUiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvYmx1cHBpZSIsInN1YiI6IjIxOGQ1NzkzLTA0NmYtNDQ4NS04ZmIxLTQ0M2E5NjEyM2FmZiIsInR5cCI6InZlcmlmeS1lbWFpbCIsImF6cCI6ImFjY291bnQiLCJub25jZSI6ImQ1ZWI5YTgzLTAxNDUtNGEwYS05NjNmLWIwZDIyNGUwNGVlZCIsImVtbCI6ImFzZGFzZEBjb3ZsZS5jb20iLCJhc2lkIjoiNmM3ZTk5NGItZTA0ZS00ZTlkLWFkNTQtZjE1MGM4NjcwYzdmLlFfQ244SlY0WFlBLmQ1MzI3MTMwLWIzY2EtNDY4Ny1iZDZkLWViZWFiODAwZTdkMyIsImFzaWQiOiI2YzdlOTk0Yi1lMDRlLTRlOWQtYWQ1NC1mMTUwYzg2NzBjN2YuUV9DbjhKVjRYWUEuZDUzMjcxMzAtYjNjYS00Njg3LWJkNmQtZWJlYWI4MDBlN2QzIn0.yrTUf2tl521Q00IUL-2dWTnugUt_ZeATa3W3IrgoRGM&client_id=account&tab_id=Q_Cn8JV4XYA
[NOTE: The line above is the RFC violation.]
This link will expire within 5 minutes.
If you didn't create this account, just ignore this message.
------=_Part_2_1488711957.1660016366185
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<html>
<body>
<p>Someone has created a Bluppie account with this email address. If this w=
as you, click the link below to verify your email address</p><p><a href=3D"=
http://localhost:8080/auth/realms/bluppie/login-actions/action-token?key=3D=
eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzODYxY2JmMy0wMWYzLTRhMmQ=
tOTg1NC02MmEyYWMyYzhjNzUifQ.eyJleHAiOjE2NjAwMTY2NjYsImlhdCI6MTY2MDAxNjM2Niw=
ianRpIjoiZDVlYjlhODMtMDE0NS00YTBhLTk2M2YtYjBkMjI0ZTA0ZWVkIiwiaXNzIjoiaHR0cD=
ovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2JsdXBwaWUiLCJhdWQiOiJodHRwOi8vbG9jY=
Wxob3N0OjgwODAvYXV0aC9yZWFsbXMvYmx1cHBpZSIsInN1YiI6IjIxOGQ1NzkzLTA0NmYtNDQ4=
NS04ZmIxLTQ0M2E5NjEyM2FmZiIsInR5cCI6InZlcmlmeS1lbWFpbCIsImF6cCI6ImFjY291bnQ=
iLCJub25jZSI6ImQ1ZWI5YTgzLTAxNDUtNGEwYS05NjNmLWIwZDIyNGUwNGVlZCIsImVtbCI6Im=
FzZGFzZEBjb3ZsZS5jb20iLCJhc2lkIjoiNmM3ZTk5NGItZTA0ZS00ZTlkLWFkNTQtZjE1MGM4N=
jcwYzdmLlFfQ244SlY0WFlBLmQ1MzI3MTMwLWIzY2EtNDY4Ny1iZDZkLWViZWFiODAwZTdkMyIs=
ImFzaWQiOiI2YzdlOTk0Yi1lMDRlLTRlOWQtYWQ1NC1mMTUwYzg2NzBjN2YuUV9DbjhKVjRYWUE=
uZDUzMjcxMzAtYjNjYS00Njg3LWJkNmQtZWJlYWI4MDBlN2QzIn0.yrTUf2tl521Q00IUL-2dWT=
nugUt_ZeATa3W3IrgoRGM&client_id=3Daccount&tab_id=3DQ_Cn8JV4XYA" rel=3D"nofo=
llow">Link to e-mail address verification</a></p><p>This link will expire w=
ithin 5 minutes.</p><p>If you didn't create this account, just ignore t=
his message.</p>
</body>
</html>
------=_Part_2_1488711957.1660016366185--

tl;dr: Add any non US-ASCII character to your templates and it will be encoded as quoted-printable.
On some cached page I found some old documentation which seem to explain the logic:
getEncoding
public static String getEncoding(DataSource ds)
Get the Content-Transfer-Encoding that should be applied to the input stream of this DataSource, to make it mail-safe.
The algorithm used here is:
If the DataSource implements EncodingAware, ask it what encoding to use. If it returns non-null, return that value.
If the primary type of this datasource is "text" and if all the bytes in its input stream are US-ASCII, then the encoding is "7bit". If more than half of the bytes are non-US-ASCII, then the encoding is "base64". If less than half of the bytes are non-US-ASCII, then the encoding is "quoted-printable".
If the primary type of this datasource is not "text", then if all the bytes of its input stream are US-ASCII, the encoding is "7bit". If there is even one non-US-ASCII character, the encoding is "base64".
Parameters:
ds - the DataSource
Returns:
the encoding. This is either "7bit", "quoted-printable" or "base64"

Related

Encrypting Headers S/MIME message/rfc822

I am looking to encrypt certain mail headers (Subject and Reply-To) which are being sent in an encrypted mail.
I am taking an entire MIME (Headers included) and successfully encrypting it.
I can send this S/MIME encrypted mail to my mail client (Thunderbird) successfully. It will be successfully decrypted and verified as signed.
However, any headers that are sent in the inner encrypted MIME are not being used by my mail client.
According to RFC-5751 I should be wrapping my mail in a message/rfc822 message but I am at a loss at how to achieve this.
Below are examples of my messages that I am creating.
My first question is, is the last MIME that I am creating the message/rfc822 correctly structured?
Is this possibly an issue with the mail client?
Can I event encrypt the Reply-To Header?
If I could get an example of a mesage/rfc822 encapsulated message that would be really helpful.
Mail to be encrypted
This will successfully result in a received mail that is signed and the Subject / Reply-To headers are interpreted correctly by the mail client.
Content-Type: multipart/signed; protocol="application/pkcs7-signature";
micalg=sha256; boundary="--_NmP-d017e0e3556f7bbc-Part_1"
From: sender#domain.com
Sender: senderdomain.com
To: recipient#domain.com
Reply-To: keepsecret#domain.com
Subject: A Secret Subject
Message-ID: <400b1383-362b-eed7-0719-6b2a2e231143>
Date: Mon, 24 Feb 2020 15:59:19 +0000
MIME-Version: 1.0
----_NmP-d017e0e3556f7bbc-Part_1
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
My Message that will be encrypted
----_NmP-d017e0e3556f7bbc-Part_1
Content-Type: application/pkcs7-signature; name=smime.p7s
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7s
MIIOCAYJKoZIhvcNAQcCoIIN+TCCDfUCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGg
gguTMIIFCDCCA/CgAwIBAgIQVz2HAGYJcTJNsPiWLx1f/TANBgkqhkiG9w0BAQsFADCBjTELMAkG
.
.
.
17p13e02JxfyCqltdb6lkOdpRZ6ZlHHuQZyBCuRtJhRN83gvcJ4d7WCxKI349NEa2/tOb8ziFGat
gzvgu+o=
----_NmP-d017e0e3556f7bbc-Part_1--
My Encrypted Mail
This encrypted mail will be received and successfully decrypted and verified (signature verified) by my mail client. Reply-To and Subject are still working as expected as they are still visible. Note: all the headers from the unencrypted mail are all still present inside the encrypted body of this message.
Sender: sender#domain.com
From: sender#domain.com
To: recipient#domain.com
Subject: A Secret Subject
Reply-To: keepsecret#domain.com
Message-ID: <400b1383-362b-eed7-0719-6b2a2e231143>
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
Content-Type: application/pkcs7-mime; smime-type=enveloped-data;
name=smime.p7m
Date: Mon, 24 Feb 2020 16:03:38 +0000
MIME-Version: 1.0
MIIYbwYJKoZIhvcNAQcDoIIYYDCCGFwCAQAxggG/MIIBuwIBADCBojCBjTELMAkG
.
.
.
O+EPVCh1fGDFwiFpDtY/z1Lv8g==
My Encapsulated message/rfc822
This message will be decrypted correctly but my client does not recognise that it was an encrypted message or verify that it was signed (Not worried about that so much). The decrypted mail is interpreted as forwarded and attached as an .eml file. However, no Subject or Reply-To headers found (they are in the encrypted mail). If I add dummy values as recommended by the RFC, those dummy values will be used by my mail client, not the encrypted ones.
Content-Type: message/rfc822; forwarded=false; boundary="--_NmP-07c15c542cedfe74-Part_1"
From: sender#domain.com
Sender: sender#domain.com
To: recipient#domain.com
Date: Mon, 24 Feb 2020 15:28:07 +0000
Message-ID: <400b1383-362b-eed7-0719-6b2a2e231143>
MIME-Version: 1.0
----_NmP-07c15c542cedfe74-Part_1
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
Content-Type: application/pkcs7-mime; smime-type=enveloped-data;
name=smime.p7m
MIIYbwYJKoZIhvcNAQcDoIIYYDCCGFwCAQAxggG/MIIBuwIBADCBojCBjTELMAkG
.
.
.
fYU1LuhSBEyymSVRzwWr2T3lrhUe5BZBoY996epZtOPdIYrz2jqUglii1+AUBpUP
UUnpr8+cHTMk/50LHdy3MqMeYA==
----_NmP-07c15c542cedfe74-Part_1
Edit: add excerpt from RFC
In RFC-8551 it states the following
In order to protect outer, non-content-related message header fields (for instance, the "Subject", "To", "From", and "Cc" fields), the
sending client MAY wrap a full MIME message in a message/rfc822
wrapper in order to apply S/MIME security services to these header
fields. It is up to the receiving client to decide how to present
this "inner" header along with the unprotected "outer" header. Given
the security difference between headers, it is RECOMMENDED that the
receiving client provide a distinction between header fields,
depending on where they are located.
When an S/MIME message is received, if the top-level protected MIME
entity has a Content-Type of message/rfc822, it can be assumed that
the intent was to provide header protection. This entity SHOULD be
presented as the top-level message, taking into account
header-merging issues as previously discussed.
RFC 822 provides a generalized description of how message headers of an email are composed and should be treated by systems they are transmitted through. RFC 5751 S/MIME 3.2 (btw, obsoleted by it successor RFC 8551 S/MIME 4.0) describes details how to use that standard to create encrypted emails.
So your approach to encrypt an email as described under My Encrypted Mail is valid and correct.
However, your approach as described under My Encapsulated message/rfc822 is not quite correct. You have obviously misinterpreted the RFC with regard to how to apply the rfc822 wrapper. The wrapper needs to be around your message before it gets encrypted, so it's going to be inside the encrypted part.
In your example, the unencrypted message (a slightly modified version Mail to be encrypted) would have to look like this:
MIME-Version: 1.0
Content-type: message/rfc822
From: sender#domain.com
Sender: senderdomain.com
To: recipient#domain.com
Reply-To: keepsecret#domain.com
Subject: A Secret Subject
Message-ID: <400b1383-362b-eed7-0719-6b2a2e231143>
Date: Mon, 24 Feb 2020 15:59:19 +0000
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha256; boundary="--_NmP-d017e0e3556f7bbc-Part_1"
----_NmP-d017e0e3556f7bbc-Part_1
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
My Message that will be encrypted
[...]
So you basically add the message/rfc822 to the message before it gets encrypted.
I have been able to verify this approach and tested the resulting message in two receiving mail clients with different results. With the macOS Mail application, the encrypted subject was not used to replace the unprotected "outer" subject, but at least, it was displayed prominently below the original headers. This is compliant with the RFC which is not very specific about the presentation:
It is up to the receiving client to decide how to present this "inner" header along with the unprotected "outer" header. Given the security difference between headers, it is RECOMMENDED that the receiving client provide a distinction between header fields, depending on where they are located.
An encrypted Reply-To header is displayed similarly, but it's email address is not honored when replying to that email.
Client Support
The support for encrypted headers in clients is somewhere between weak and non-existent. The results of some tests:
No client supports replacing the "outer" headers by the "inner" encrypted ones
Apple Mail (macOS) displays the inner headers prominently within the message
Thunderbird displays the encrypted part including its headers as a forwarded message
Outlook does not display the encrypted part, but instead confusingly displays just an empty message with an attachment (which is the encrypted message)
Alternative approaches
There is a seemingly promising approach proposed in this draft for Protected Headers for Cryptographic E-mail (work in progress). The idea is to include the protected headers as a separate part in a multipart message. This part will be rendered inline by agnostic clients, while at the same time, it can be properly processed by supporting clients.

Incomplete attachments remain attached to the mail

I am using mimedefang filtering tool. In the configuration, I strip out all the attachments and forward it to another address. For particular sender, I can see milter changes the header Content-Type from application/pdf and multipart-mixed. In the received email on outlook, when I open the pdf using text editor (it contains content like ("This is a multi-part message in MIME format..." followed by some random numbers "------------=_1525668389-64274-8--").
Can anyone guess why this might be happening?
Multi-part messages (like those with attachments) have their parts divided by a boundary. This boundary is between 1 and 70 characters and must not appear anywhere in the anywhere within the encapsulated parts of the message (between boundaries).
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08jU534c0p
This is a message with multiple parts in MIME format.
--gc0p4Jq0M2Yt08jU534c0p
Content-Type: text/html; charset=UTF-8
<html><head></head><body>This is the HTML body of the message.</body></html>
--gc0p4Jq0M2Yt08jU534c0p
Content-Type: text/plain
This is the body of the message.
--gc0p4Jq0M2Yt08jU534c0p
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==
--gc0p4Jq0M2Yt08jU534c0p--
I suspect that somewhere between mimedefang and your milter configuration, the boundaries are getting mangled or included into the attachment can causing them to be corrupted.

How to compose multipart MIME mixed message to display it correctly in Outlook

I am using java code to compose and send e-mail messages to MS Outlook 365 accounts.
The message is composed as following:
plain text part;
html part containing only table;
plain text part;
File attachment.
I expect that outlook displays part 1-3 as message body and the last part as attachment. Instead it displays only first part as message itself and puts the rest parts as separate attachments.
I have played with different headers, but result is the same. Is it some feature of MS stack (exchange, outlook, etc.) or I did something wrong?
The raw message is here
Date: Mon, 5 Jun 2017 12:01:47 -0700 (PDT)
From: no-reply#example.com
To: consumer#example.com
Message-ID: <some id>
Subject: Test email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----=_Part_0_2350964.1496689307498"
------=_Part_0_2350964.1496689307498
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Content before table
------=_Part_0_2350964.1496689307498
Content-Type: text/html; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<html><body><table><tr><td>First parameter</td><td>value</td></tr><tr><td>Second row</td><td>42</td></tr></table></body></html>
------=_Part_0_2350964.1496689307498
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
This content should be placed after table
------=_Part_0_2350964.1496689307498
Content-Type: application/octet-stream;
name="report.xls"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.xls"
0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAA
<skipped>
------=_Part_0_2350964.1496689307498--
As a result I see only "Content before table" line, two attachments like "ATT01.html" and "ATT02.txt" and my original attachment "report.xls".
I have found one related topic at some forum saying that this problem should not appear since Outlook 2010 or so, but I can see it in the latest version both in desktop app and the web application.
The workaround solution to put everything in html part requires more work, because text parts are generated by other components.
You are grouping the parts incorrectly. It must be
1. Outer message, multipart/mixed
2. multipart/related
2.a text/plain
2.b text/html
3. attachment.

Gmail API - plaintext word wrapping

When sending emails using the Gmail API, it places hard line breaks in the body at around 78 characters per line. A similar question about this can be found here.
How can I make this stop? I simply want to send plaintext emails through the API without line breaks. The current formatting looks terrible, especially on mobile clients (tested on Gmail and iOS Mail apps).
I've tried the following headers:
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Am I missing anything?
EDIT: As per Mr.Rebot's suggestion, I've also tried this with no luck:
Content-Type: mixed/alternative
EDIT 2: Here's the exact format of the message I'm sending (attempted with and without the quoted-printable header:
From: Example Account <example1#example.com>
To: <example2#example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Subject: This is a test!
Date: Tue, 18 Oct 2016 10:46:57 -GMT-07:00
Here is a long test message that will probably cause some words to wrap in strange places.
I take this full message and Base64-encode it, then POST it to /gmail/v1/users/{my_account}/drafts/send?fields=id with the following JSON body:
{
"id": MSG_ID,
"message": {
"raw": BASE64_DATA
}
}
Are you running the content through a quoted printable encoder and sending the encoded content value along with the header or expecting the API to encode it for you?
Per wikipedia it seems like if you add soft line breaks with = less than 76 characters apart as the last character on arbitrary lines, they should get decoded out of the result restoring your original text.
UPDATE
Try sending with this content whose message has been quoted-printable encoded (base64 it):
From: Example Account <example1#example.com>
To: <example2#example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Subject: This is a test!
Date: Tue, 18 Oct 2016 10:46:57 -GMT-07:00
Here is a long test message that will probably cause some words to wrap in =
strange places.
I'm assuming you have a function similar to this:
1. def create_message(sender, to, cc, subject, message_body):
2. message = MIMEText(message_body, 'html')
3. message['to'] = to
4. message['from'] = sender
5. message['subject'] = subject
6. message['cc'] = cc
7. return {'raw': base64.urlsafe_b64encode(message.as_string())}
The one trick that finally worked for me, after all the attempts to modify the header values and payload dict (which is a member of the message object), was to set (line 2):
message = MIMEText(message_body, 'html') <-- add the 'html' as the second parameter of the MIMEText object constructor
The default code supplied by Google for their gmail API only tells you how to send plain text emails, but they hide how they're doing that.
ala...
message = MIMEText(message_body)
I had to look up the python class email.mime.text.MIMEText object.
That's where you'll see this definition of the constructor for the MIMEText object:
class email.mime.text.MIMEText(_text[, _subtype[, _charset]])
We want to explicitly pass it a value to the _subtype. In this case, we want to pass: 'html' as the _subtype.
Now, you won't have anymore unexpected word wrapping applied to your messages by Google, or the Python mime.text.MIMEText object
This exact issue made me crazy for a good couple of hours, and no solution I could find made any difference.
So if anyone else ends up frustrated here, I'd thought I'd just post my "solution".
Turn your text (what's going to be the body of the email) into simple HTML. I wrapped every paragraph in a simple <p>, and added line-breaks (<br>) where needed (e.g. my signature).
Then, per Andrew's answer, I attached the message body as MIMEText(message_text, _subtype="html"). The plain-text is still not correct AFAIK, but it works and I don't think there's a single actively used email-client out there that doesn't render HTML anymore.

GMail displays plain text email instead HTML

My Rails 3 application sends out emails in both plain text and HTML formats. I have tested it locally using RoundCube and Squirrel Mail clients and they both display HTML version with images, links, etc. GMail on the other hand chooses plain text format. Any idea what's causing this?
Delivered-To: test#gmail.com
Received: by 10.42.166.2 with SMTP id m2cs16081icy;
Thu, 3 Mar 2011 17:01:48 -0800 (PST)
Received: by 10.229.211.138 with SMTP id go10mr1544841qcb.195.1299200507499;
Thu, 03 Mar 2011 17:01:47 -0800 (PST)
Return-Path: <info#example.com>
Received: from beta.example.com (testtest.test.com [69.123.123.123])
by mx.google.com with ESMTP id j14si1690118qcu.136.2011.03.03.17.01.46;
Thu, 03 Mar 2011 17:01:46 -0800 (PST)
Received-SPF: neutral (google.com: 69.123.123.123 is neither permitted nor denied by best guess record for domain of info#example.com) client-ip=69.123.123.123;
Authentication-Results: mx.google.com; spf=neutral (google.com: 69.123.123.123 is neither permitted nor denied by best guess record for domain of info#example.com) smtp.mail=info#example.com
Received: from localhost.localdomain (localhost [127.0.0.1])
by beta.example.com (Postfix) with ESMTP id F3C273A3EC
for <test#gmail.com>; Fri, 4 Mar 2011 01:01:45 +0000 (UTC)
Date: Fri, 04 Mar 2011 01:01:45 +0000
From: info#example.com
To: test#gmail.com
Message-ID: <4d7039f9e9d3e_3449482ab7831658#test.mail>
Subject: Your example account was activated.
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_4d7039f9e6967_3449482ab7831370";
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_4d7039f9e6967_3449482ab7831370
Date: Fri, 04 Mar 2011 01:01:45 +0000
Mime-Version: 1.0
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Content-ID: <4d7039f9e95ed_3449482ab7831519#test.mail>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<p><img border="0" src="http://example.com/images/logo.png" alt="example logo" /></p>
<p>Congratulations, Test!</p>
<p>
Your <a style="text-decoration:none;color:#ef4923;" href="http://example.com/">example</a> account was activated.
</p>
</body>
</html>
----==_mimepart_4d7039f9e6967_3449482ab7831370
Date: Fri, 04 Mar 2011 01:01:45 +0000
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Content-ID: <4d7039f9e8b0e_3449482ab78314b7#test.mail>
Congratulations, Test!
Your example.com account was activated.
----==_mimepart_4d7039f9e6967_3449482ab7831370--
Try switching the order of the parts of the message, putting the HTML part after the plain-text part. It might work :).
NOTE: I cannot remember now where I read this (or if I for sure even
did), but the reason switching might help is because I think the
preferred part of the message may be the last part.
Update: I found a place where it says that parts in a multipart MIME message should be in order of increasing preference -- here, in section 7.2.3 (edit: latest version here; thanks #ALEXintlsos!), starting with the third to last paragraph.
Update: Here is a quote of section 7.2.3, (see https://stackoverflow.com/help/referencing):
7.2.3 The Multipart/alternative subtype
The multipart/alternative type is syntactically identical to multipart/mixed,
but the semantics are different. In particular, each of the parts is an
"alternative" version of the same information. User agents should recognize
that the content of the various parts are interchangeable. The user agent
should either choose the "best" type based on the user's environment and
preferences, or offer the user the available alternatives. In general, choosing
the best type means displaying only the LAST part that can be displayed. This
may be used, for example, to send mail in a fancy text format in such a way
that it can easily be displayed anywhere:
From: Nathaniel Borenstein <nsb#bellcore.com>
To: Ned Freed <ned#innosoft.com>
Subject: Formatted text mail
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary=boundary42
--boundary42
Content-Type: text/plain; charset=us-ascii
...plain text version of message goes here....
--boundary42
Content-Type: text/richtext
.... richtext version of same message goes here ...
--boundary42
Content-Type: text/x-whatever
.... fanciest formatted version of same message goes here
...
--boundary42--
In this example, users whose mail system understood the "text/x-whatever"
format would see only the fancy version, while other users would see only the
richtext or plain text version, depending on the capabilities of their system.
In general, user agents that compose multipart/alternative entities should
place the body parts in increasing order of preference, that is, with the
preferred format last. For fancy text, the sending user agent should put the
plainest format first and the richest format last. Receiving user agents should
pick and display the last format they are capable of displaying. In the case
where one of the alternatives is itself of type "multipart" and contains
unrecognized sub-parts, the user agent may choose either to show that
alternative, an earlier alternative, or both.
NOTE: From an implementor's perspective, it might seem more sensible to reverse
this ordering, and have the plainest alternative last. However, placing the
plainest alternative first is the friendliest possible option when
multipart/alternative entities are viewed using a non-MIME- compliant mail
reader. While this approach does impose some burden on compliant mail readers,
interoperability with older mail readers was deemed to be more important in
this case.
It may be the case that some user agents, if they can recognize more than one
of the formats, will prefer to offer the user the choice of which format to
view. This makes sense, for example, if mail includes both a nicely-formatted
image version and an easily-edited text version. What is most critical, however,
is that the user not automatically be shown multiple versions of the same data.
Either the user should be shown the last recognized version or should
explicitly be given the choice.