Rails: Attachments not visible in mail clients when additional inline attachments present

Created on 25 Aug 2011  Â·  52Comments  Â·  Source: rails/rails

When assembling an email with mixed inline / normal attachments, only the inline attachments (i.e. images) are shown. Some mail clients don't detect the attached files, the most prominent being Thunderbird and Outlook.

Code used with ActionMailer 3.0.10:

class MultipartTest < ActionMailer::Base
  def test_email(recipient)
    attachments['file1.pdf'] = File.read('/somewhere/file1.pdf')
    attachments['file2.pdf'] = File.read('/somewhere/file2.pdf')
    attachments.inline['image1.gif'] = File.read('/somewhere/image1.gif')
    mail(
      :to => recipient,
      :subject => 'Multipart test'
    )
  end
end

The generated email comes as follows:

multipart/related
  multipart/alternative
    text/plain
    text/html
  attachment (disposition: inline, image1)
  attachment (disposition: attachment, file1)
  attachment (disposition: attachment, file2)

While ActionMailer::Base::set_content_type() chooses multipart/related as soon as it detects at least one inline attachment, mail clients always wrap the attached files (disposition: attachment) in an additional multipart/mixed layer, conforming to RFC 2046, Section 5.1.3.

When leaving out the inline attachement, the MIME type generated by ActionMailer is correct (multipart/mixed) and the attachments are visible.

Here are some MIME layouts, as generated by mail clients:

multipart/mixed
  multipart/alternative
    text/plain
    multipart/related
      text/html
      attachment (disposition: inline, image1)
  attachment (disposition: attachment, file1)
  attachment (disposition: attachment, file2)

or:

multipart/mixed
  multipart/related
    multipart/alternative
      text/plain
      text/html
    attachment (disposition: inline, image1)
  attachment (disposition: attachment, file1)
  attachment (disposition: attachment, file2)
actionmailer attached PR pinned

Most helpful comment

@prathamesh-sonpatki @maclover7 @rafaelfranca @matthewd I imagine there is some satisfaction to be gained from being able to close Rails' oldest open issue. Any chance of an update from someone on the Rails team regarding this open PR?

All 52 comments

I can confirm this problem. Our workaround for now is to avoid inline attachments at all if there are any normal attachments.

I can confirm this as well.

Confirm, Outlook can see attachments only after removing inline attachments.

Yep, same here, on my iMac, the normal attachments show in the attachments pulldown, but the email itself has no indication that there are normal attachments.

Maybe a blessing? The inline attachment is just a little company logo in the emails 'signature' , something my users like, but I've always found annoying!

Is this still an issue? cc @spastorino @tenderlove

I repeated the tests using ActionMailer 3.2.3 and I see no difference in the generated MIME structure: there's no "multipart/mixed".
So: this issue hasn't been solved, as far as I can see.

I confirm this issue as well.

@jonleighton is your work on 37signals/mail_view#17 useful here, too? Any plans on fixing this in Rails as well as that app?

@steveklabnik I want to fix this at some point; it might become a necessity through my work actually. No ETA though.

Fair enough! Thanks.

This hit me too, in particular this is blocking sending out e-mails with Passbook attachments (while also including inline files) because the iOS Mail client refuses to see the passkit file attached due to this issue. The Mac Mail.app client however shows it fine.

I've come up with a workaround for this bug. Add this method to your mailer:

  # Workaround for https://github.com/rails/rails/issues/2686
  def fix_mixed_attachments
    mail = Mail.new
    mail.delivery_method delivery_methods[delivery_method.to_sym], public_send("#{delivery_method}_settings")

    related = Mail::Part.new
    related.content_type = @_message.content_type
    @_message.parts.select { |p| !p.attachment? || p.inline? }.each { |p| related.add_part(p) }
    mail.add_part related

    mail.header       = @_message.header.to_s
    mail.content_type = nil
    @_message.parts.select { |p| p.attachment? && !p.inline? }.each { |p| mail.add_part(p) }

    @_message = mail
  end

Then call it at the end of your action method:

  def notification(...)
    attachments['omg.pdf'] = ...
    attachments.inline['wtf.png'] = ...

    mail(...)
    fix_mixed_attachments
  end

YMMV etc, but it works for me.

We're seeing that when attaching a text/csv when we have an html and text view a multipart/alternative wrapper (which includes another multipart/alternative and the attachment itself). We're not including any inline attachments. The outer multipart/alternative wrapper isn't understood by many mail clients. Is this a different issue or related?

So based on the original bug and the examples of real mail clients and the workaround posted by @jonleighton it seems like the solution would be to just move attachment (disposition: inline, image1) (for example) underneath a multipart/related heading?

We see this issue with Outlook 365 only. The same emails to gmail appear to be displayed fine.

Either way, the workaround from @jonleighton seems to work for us. Thanks!

Ping - any progress on getting a fix for this into Rails proper?

Improvement to the workaround earlier:

  • Aborts if there are no regular attachments, leaving the original message as it was
  • Calls a higher-level AM method (wrap_delivery_behavior!) that preserves settings like :perform_deliveries and raise_delivery_errors
  def fix_mixed_attachments
    # do nothing if we have no actual attachments
    return if @_message.parts.select { |p| p.attachment? && !p.inline? }.none?

    mail = Mail.new

    related = Mail::Part.new
    related.content_type = @_message.content_type
    @_message.parts.select { |p| !p.attachment? || p.inline? }.each { |p| related.add_part(p) }
    mail.add_part related

    mail.header       = @_message.header.to_s
    mail.content_type = nil
    @_message.parts.select { |p| p.attachment? && !p.inline? }.each { |p| mail.add_part(p) }

    @_message = mail
    wrap_delivery_behavior!(delivery_method.to_sym)
  end

Not using this in production yet but made the improvements during my testing in development.

If you find something that works well, you should submit a pull request!

yyyc514's workaround seems to have fixed it for me. Thank you.

P.S. Would be nice to see this merged into the main code though...

yyyc514's workaround also worked for me. For some reason Johns workaround worked in most cases but the attachment vanished in Outlook 2010 but was visible in Outlook 2013. When the missing email was forwarded back to me Outlook 2013 could now see the attachment. weird!

I used to have the same problem in my app. I've solved it changing message structure to this:

multipart/related
  multipart/related
    multipart/alternative
      text/plain
      text/html
    image/jpeg   #inline
    image/jpeg   #inline
    ...          #other inline attachments  
  multipart/related
    application/pdf #non-inline
    text/plain      #non-inline
    ...             #other non-inline attachments

Maybe it seems strange, that all non-inline attachments are wrapped by extra 'related'. I've tested this structure on different mail clients - it works fine. If we don't wrap them, and we have plain/text attachment in our message, some mail clients (gmail as well) parse this attachment as mail text part (it's displayed in previews).

I'd like to fix this issue, but i can't find code where the mail structure is formed... Maybe this is issue of mail gem?

cc / @steveklabnik @jonleighton

Building the structure is part of ActionMailer, you'll find the code there. Mail just provides the building blocks, nothing more.

So should a PR be made to ActionMailer like:
http://github.com/rails/rails/commit/311d99eef01c268cedc6e9b3bdb9abc2ba5c6bfa
Or should a PR be made into Mail?

I believe we should fix Mail gem if it is broken. Who can investigate this issue and work on it?

This isn't Mail's problem (as the division of labor currently stands). Mail is the low-level email library and Rails is responsible for building the MIME envelopes for multi-part emails. Someone on the Rails team needs to care about this issue - which doesn't seem to be the case.

The Rails team don't have time to fix all the issues as I said before, who want to investigate and provide a pull request?

I'd be happy to, but I don't really know what the actual CORRECT solution is. You see my code above... I could merge it into AM proper... but is this the actual correct solution to the problem? I guess I was hoping someone would come out of the woodwork with an _authoritative_ fix, vs "this works for me".

I'd worry without some official reference that the Core team wouldn't be as likely to merge the pull request since if they had run into this personally you'd think they'd have fixed it already. So those are my fears.

So it's the "investigate" part I guess I'd be hung up on. :)

I'll suggest to you open a pull request with your current fix. As you said we don't have this problem as would be way easier if we were discussing about a failing test to understand and some work in progress patch.

Could you do it?

No idea how to write a failing test. Some email clients just don't like _some_ aspect of how you currently build the MIME packets... not even sure if it's right or wrong we're talking about. Could be those clients (even if popular) are the problem... so this isn't really something that has a failing test. It's just that doing it an alternative way _seems_ to work better across multiple clients.

This thread already should contain enough information to discuss the issue. All you could do is write a test that looked for the MIME structure recommended here and then failed... but that's no good unless people understand the issue and agree that the MIME structure above is the right way to go.

Can we agree which one of these is correct?

multipart/mixed
  multipart/alternative
    text/plain
    multipart/related
      text/html
      attachment (disposition: inline, image1)
  attachment (disposition: attachment, file1)
  attachment (disposition: attachment, file2)
multipart/mixed
  multipart/related
    multipart/alternative
      text/plain
      text/html
    attachment (disposition: inline, image1)
  attachment (disposition: attachment, file1)
  attachment (disposition: attachment, file2)

Isn't that going to depend on the author's intent? IMO, the former is generally more "correct": the (inline) images "belong" to the HTML part, and should be ignored by anything that chooses to use the Plain part.

But the most realistic definition of correct will be in which elicits a more suitable behaviour from more clients, if there's any difference.

thx yyyc514 for the fix, it works for me.

But it omits the BCC from the header. I inserted this

mail.bcc = @_message.header[:bcc].value

as a workaround. Maybe there is a better way.

Maybe someone else stumbles over this too.

@swelther Are you sure you're using my code? There is a line that copies over all the headers whole sale... if it didn't work a lot more than the BCC would be broken. Have you tried to track down the issue any further? Honestly not sure why BCC would be some sort of edge case - though I haven't dug in further either.

mail.header       = @_message.header.to_s

@yyyc514 Yes, it's your code. I was wondering too, maybe it is a documented behavior in newer Rails versions?

def fix_mixed_attachments
  # do nothing if we have no actual attachments
  return if @_message.parts.select { |p| p.attachment? && !p.inline? }.none?

  mail = Mail.new

  related = Mail::Part.new
  related.content_type = @_message.content_type
  @_message.parts.select { |p| !p.attachment? || p.inline? }.each { |p| related.add_part(p) }
  mail.add_part related

  mail.header = @_message.header.to_s
  mail.content_type = nil
  @_message.parts.select { |p| p.attachment? && !p.inline? }.each { |p| mail.add_part(p) }

  @_message = mail
  wrap_delivery_behavior!(delivery_method.to_sym)
end

this is what I get from @_message.header.to_s in my spec:

> @_message.header.to_s
=> "From: [email protected]\r\nTo: [email protected]\r\nSubject: blah\r\nMime-Version: 1.0\r\nContent-Type: multipart/related;\r\n boundary=\"--==_mimepart_5465f952f0ba1_7dde50133838765\";\r\n charset=UTF-8\r\n"

no bcc. But it is set in the header:

> @_message.header[:bcc].value
=> ["[email protected]"]

Maybe Rails 4.0.10 (or active mailer or whatever) acts in this case a bit different than previous versions? IIRC I tried this on a server and got the same behavior as in my spec.

Oh, that seems intentional then. I'll look at it later. Perhaps you can paste another full code excerpt here and add the BCC fix... I'm guessing it's something intentional relating to the strange nature of BCC.

sure, no big deal, it works for me :grinning:

so if anyone stumbles over not-copied BCC values use this version:

  def fix_mixed_attachments
    # do nothing if we have no actual attachments
    return if @_message.parts.select { |p| p.attachment? && !p.inline? }.none?

    mail = Mail.new

    related = Mail::Part.new
    related.content_type = @_message.content_type
    @_message.parts.select { |p| !p.attachment? || p.inline? }.each { |p| related.add_part(p) }
    mail.add_part related

    mail.header       = @_message.header.to_s
    mail.bcc          = @_message.header[:bcc].value # copy bcc manually because it is omitted in header.to_s
    mail.content_type = nil
    @_message.parts.select { |p| p.attachment? && !p.inline? }.each { |p| mail.add_part(p) }

    @_message = mail
    wrap_delivery_behavior!(delivery_method.to_sym)
  end

From Mail's bcc_field.rb line 24:

#  mail[:bcc].encoded   #=> ''      # Bcc field does not get output into an email

So BCC is a magic field that is never output (why to_s fails) but rather is (I'd imagine) used internally by Mail (or other gems) during the delivery process. So that's why it's necessary to copy it in such fashion. There may be a simpler way to just copy the whole header object over as-is (or rather clone it), but I didn't spend any time looking into that. I just wanted to answer the question of "why".

thx for clarifying @yyyc514. Sounds reasonable. Maybe someone gets bored and develops a simpler version :grinning:

This issue has been automatically marked as stale because it has not been commented on for at least
three months.

The resources of the Rails team are limited, and so we are asking for your help.

If you can still reproduce this error on the 4-2-stable, 4-1-stable branches or on master,
please reply with all of the information you have about it in order to keep the issue open.

Thank you for all your contributions.

I can still reproduce this issue with Rails 4.2.4

The same e-mail sent to Apple Mail is correctly readable, but sent to a iPhone the inline logo is readable, but the attachments are not.

There has already been some discussion and solutions in the past: http://stackoverflow.com/questions/1118592/problem-sending-multipart-mail-using-actionmailer

But the provided solutions there do not work with Rails 4.x

@javinto any more details?

@dmitry no my use case basically is similar to that of icanhasserver. My problem occurs with Rails 4.1 but I can still repeat it with Rails 4.2.4 on my IOS 9.02 device.

@javinto @dmitry I have a failing test now - hopefully the fix shouldn't be too painful

I just got burned by this this morning @pixeltrix, came looking and 10 hours prior someone's found it also. Let me know if I can do anything to help on this issue.

@dannolan it'd be helpful if you can cast your eye over the possible structures below and point out any flaws:

# single, none
text/html

# single, inline
multipart/related
  text/html
  image/png

# single, attachment
multipart/mixed
  text/html
  application/pdf

# single, mixed
multipart/mixed
  multipart/related
    text/html
    image/png
  application/pdf

# multiple, none
multipart/alternative
  text/plain
  text/html

# multiple, inline
multipart/related
  multipart/alternative
    text/plain
    text/html
  image/png

# multiple, attachment
multipart/mixed
  multipart/alternative
    text/plain
    text/html
  application/pdf

# multiple, mixed
multipart/mixed
  multipart/related
    multipart/alternative
      text/plain
      text/html
    image/png
  application/pdf

I'm sorry. After a simple test from Apple Mail I cannot recognize the structures below. I'm not into mail structures.
How can I recognize the indented structures from the Source view? There is no multipart/related used by Apple mail in my tests.
Does your list conform to the official specs?

Op 16 okt. 2015, om 12:04 heeft Andrew White [email protected] het volgende geschreven:

@dannolan https://github.com/dannolan it'd be helpful if you can cast your eye over the possible structures below and point out any flaws:

single, none

text/html

single, inline

multipart/related
text/html
image/png

single, attachment

multipart/mixed
text/html
application/pdf

single, mixed

multipart/mixed
multipart/related
text/html
image/png
application/pdf

multiple, none

multipart/alternative
text/plain
text/html

multiple, inline

multipart/related
multipart/alternative
text/plain
text/html
image/png

multiple, attachment

multipart/mixed
multipart/alternative
text/plain
text/html
application/pdf

multiple, mixed

multipart/mixed
multipart/related
multipart/alternative
text/plain
text/html
image/png
application/pdf
—
Reply to this email directly or view it on GitHub https://github.com/rails/rails/issues/2686#issuecomment-148673410.

@javinto I'm using the pattern in the original report by @icanhasserver - top level is message content type and then each indent is a different nested part level and each line is a part and the text is the content type.

this problem is still present in rails 5. fortunately @jonleighton's fix still works.

PR to fix this, with test, at https://github.com/rails/rails/pull/26445

@pixeltrix I agree those possible structures are accurate, and are what I use (well, I never don't have a text/plain part :) ). If your inline image was totally superfluous, and could be hidden from the text/plain user (e.g. a footer image), then you could theoretically do mixed(alternative(plain, related(html, inline)), attachment), but better not do that currently.

this structure is working fine in all mail client except microsoft outlook mail. I am wonder why in microsoft outlook mail text/html is not rendering in the body instead is coming as attachment. ..Can anyone help me out ?

@prathamesh-sonpatki @maclover7 @rafaelfranca @matthewd I imagine there is some satisfaction to be gained from being able to close Rails' oldest open issue. Any chance of an update from someone on the Rails team regarding this open PR?

Was this page helpful?
0 / 5 - 0 ratings