[wp-trac] [WordPress Trac] #15448: wp_mail() sets Content-Type header twice for multipart emails

WordPress Trac noreply at wordpress.org
Sun Aug 17 14:30:21 UTC 2025


#15448: wp_mail() sets Content-Type header twice for multipart emails
-------------------------------------------------+-------------------------
 Reporter:  rmccue                               |       Owner:  SirLouen
     Type:  defect (bug)                         |      Status:  accepted
 Priority:  high                                 |   Milestone:  6.9
Component:  Mail                                 |     Version:  2.8
 Severity:  normal                               |  Resolution:
 Keywords:  has-patch has-unit-tests has-test-   |     Focuses:
  info needs-testing                             |
-------------------------------------------------+-------------------------
Changes (by SirLouen):

 * status:  reviewing => accepted
 * priority:  normal => high
 * owner:  SergeyBiryukov => SirLouen
 * version:   => 2.8
 * milestone:  Future Release => 6.9
 * keywords:  has-patch has-unit-tests has-test-info reported-upstream =>
     has-patch has-unit-tests has-test-info needs-testing
 * type:  feature request => defect (bug)


Comment:

 After reviewing the PHPMailer library for a good while, I've noticed that
 it was never a problem of this library as the members from StackOverflow
 suggested. I was able to create a full example in PHPMailer and I wasn't
 able to reproduce it there, so I came to the WP code and found the
 problem.

 Currently, the construction of the `Content-Type` is currently being done
 here:
 https://github.com/WordPress/wordpress-
 develop/blob/dd2d7efd9dc11a362056be6cd7fc75ccd6b0cf22/src/wp-
 includes/pluggable.php#L517-L519

 The issue is that this conditional only happens if there are `$headers`.
 Generally there are other headers, which is not strictly necessary to send
 a multipart.

 @rmccue was very tricky because he provided a superb example.
 https://core.trac.wordpress.org/ticket/15448#comment:70
 Including an extra random header to trigger this, but still his code was
 not working.

 Why?

 Because it was incorrectly formatted.
 [https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html According to the
 RFC1341], there should be an empty line between each of the Content-Types
 inside the body.

 In the @rmccue example:

 {{{
 $body = '--TestBoundary
 Content-Type: text/plain; charset=UTF-8

 This is a test email body.
 --TestBoundary
 Content-Type: text/html; charset=UTF-8
 <html><body>This is a test email body.</body></html>
 --TestBoundary--
 ';
 $headers = [
         'Example-Custom: value',
         'Content-Type: multipart/alternative; boundary="TestBoundary"',
 ];

 wp_mail( 'test at example.com', 'Test', $body, $headers );
 }}}

 There is no space in between the `Content-Type` for the `text/html` and
 the HTML content. This is why, despite of providing this little extra
 header to trigger the conditional, it is not working.

 The question is, why handle the `Content-Type` so late and in the Headers
 section if the `Content-Type` is [https://github.com/WordPress/wordpress-
 develop/blob/dd2d7efd9dc11a362056be6cd7fc75ccd6b0cf22/src/wp-
 includes/pluggable.php#L320-L335 being handled much earlier] and
 specifically in the `Content-Type` part that will be straightly passed to
 the `$phpmailer->Content-Type` instance? It happens to be a bug introduced
 in 2.8, more specifically in [11136], wrongly added with the requirement
 of extra headers.

 My solution is simple: removing the late `Content-Type` handling for
 multipart and bringing it back to the specific `content-type` section.
 Furthermore, I provide a little regex so we can manage every single type
 of multipart available (not only `alternative`, but also `mixed`,
 `related`, etc…).

 I've added an additional unit test to check for this. Despite this having
 now been completely reviewed and ready to be shipped, I would like to add
 a pair of manual tests to fully close this ticket.

 == Testing Information

 To test this, I would recommend doing the two following tests:

 1. Create a little plugin that sends a `multipart/alternative` email, and
 check the results.
 2. Same but creating a `multipart/mixed`. This will also prove that this
 won't cause a regression with [11136].

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/15448#comment:86>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list