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.
[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>>]
[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"
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
d1(firstMail.new) andd2(second `Mail.new) should be essentially the same message, but they are no longer.But we can see that a round-trip via
to_sresults 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:Tested with:
Mail 2.9.0
Ruby 3.3.6