Skip to content

Mail gem shouldn't reorder parts of MIME messages #1658

Description

@Supermathie

We've discovered that the Mail gem can reorder the MIME parts of messages when marshalling to a string - this can cause significant behaviour differences.

Here is an example:

demo.eml

[1] pry(main)> require 'mail'
=> true

[2] pry(main)> d = File.open('demo.eml').read;

[3] pry(main)> d1 = Mail.new(d)
=> #<Mail::Message:7180, Multipart: true, Headers: <From: Bilbo Baggins via tor-relays <tor-relays@lists.torproject.org>>, <Reply-To: Bilbo Baggins <bbaggins@shire.net>>, <Message-ID: <20260103080033.56fdf890@shire.net>>, <Subject: [tor-relays] Subject>, <Content-Type: multipart/mixed; boundary="===============8958541500975114832==">>

[4] pry(main)> d2 = Mail.new(Mail.new(d).to_s)
=> #<Mail::Message:7200, Multipart: true, Headers: <Date: Tue, 13 Jan 2026 16:37:16 -0500>, <From: Bilbo Baggins via tor-relays <tor-relays@lists.torproject.org>>, <Reply-To: Bilbo Baggins <bbaggins@shire.net>>, <Message-ID: <20260103080033.56fdf890@shire.net>>, <Subject: [tor-relays] Subject>, <MIME-Version: 1.0>, <Content-Type: multipart/mixed; boundary="===============8958541500975114832==">, <Content-Transfer-Encoding: 7bit>>

[5] pry(main)> d1.parts
=> [#<Mail::Part:7220, Multipart: true, Headers: <Content-Type: multipart/signed; boundary="Sig_/gizYC_1dGsAzUHvksdaMIe2"; protocol="application/pgp-signature"; micalg=pgp-sha256>>,
 #<Mail::Part:7240, Multipart: false, Headers: <MIME-Version: 1.0>, <Content-Type: text/plain; charset="us-ascii">, <Content-Transfer-Encoding: 7bit>, <Content-Disposition: inline>>]

[6] pry(main)> d2.parts
=> [#<Mail::Part:7260, Multipart: false, Headers: <MIME-Version: 1.0>, <Content-Type: text/plain; charset=us-ascii>, <Content-Transfer-Encoding: 7bit>, <Content-Disposition: inline>, <Content-ID: <6966bb0c8a4cb_390c51bf8532@mars.mail>>>,
 #<Mail::Part:7280, Multipart: true, Headers: <Content-Type: multipart/signed; boundary="Sig_/gizYC_1dGsAzUHvksdaMIe2"; micalg=pgp-sha256; protocol="application/pgp-signature">, <Content-Transfer-Encoding: 7bit>>]

d1 (first Mail.new) and d2 (second `Mail.new) should be essentially the same message, but they are no longer.

But we can see that a round-trip via to_s results in a message fundamentally different than the original (we do this in Discourse to potentially store a truncated version) - the problem is easily reproduced with the following:

[10] pry(main)> d1.text_part.to_s
=> "Content-Type: text/plain;\r\n charset=US-ASCII\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nHere is the actual message content.\r\n\r\n-- =\r\n\r\nkind regards\r\nBilbo\r\n"

[11] pry(main)> d2.text_part.to_s
=> "MIME-Version: 1.0\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: inline\r\nContent-ID: <6966bb0c8a4cb_390c51bf8532@mars.mail>\r\n\r\n_______________________________________________\r\ntor-relays mailing list -- tor-relays@lists.torproject.org\r\nTo unsubscribe send an email to tor-relays-leave@lists.torproject.org\r\n"

Tested with:
Mail 2.9.0
Ruby 3.3.6

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions