Problem sending multipart mail using ActionMailer

19,107

Solution 1

I suspect the issue is you're defining the overall email as multipart/alternative, suggesting each part is just an alternate view of the same message.

I use something like the following to send mixed html/plain emails with attachments, and it seems to work OK.

class InvoiceMailer < ActionMailer::Base

  def invoice(invoice)
    from          CONFIG[:email]
    recipients    invoice.email
    subject       "Bevestiging Inschrijving #{invoice.course.name}"
    content_type  "multipart/mixed"

    part(:content_type => "multipart/alternative") do |p|
      p.part "text/html" do |p|
        p.body = render_message 'invoice_html', :invoice => invoice
      end

      p.part "text/plain" do |p|
        p.body = render_message 'invoice_plain', :invoice => invoice
      end
    end

    pdf = Prawn::Document.new(:page_size => 'A4')
    PDFRenderer.render_invoice(pdf, invoice)
    attachment :content_type => "application/pdf", :body => pdf.render, :filename => "factuur.pdf"

    invoice.course.course_files.each do |file|
      attachment :content_type => file.content_type, :body => File.read(file.full_path), :filename => file.filename
    end
  end

end

Solution 2

@jcoleman is correct but if you don't want to use his gem then this may be a better solution:

class MyEmailerClass < ActionMailer::Base
  def my_email_method(address, attachment, logo)

    # Add inline attachments first so views can reference them
    attachments.inline['logo.png'] = logo

    # Call mail as per normal but keep a reference to it
    mixed = mail(:to => address) do |format|
      format.html
      format.text
    end

    # All the message parts from above will be nested into a new 'multipart/related'
    mixed.add_part(Mail::Part.new do
      content_type 'multipart/related'
      mixed.parts.delete_if { |p| add_part p }
    end)
    # Set the message content-type to be 'multipart/mixed'
    mixed.content_type 'multipart/mixed'
    mixed.header['content-type'].parameters[:boundary] = mixed.body.boundary

    # Continue adding attachments normally
    attachments['attachment.pdf'] = attachment
  end
end

This code begins by creating the following MIME hierarchy:

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

After the call to mail we create a new multipart/related part and add the children of the existing part (removing them as we go). Then we force the Content-Type to be multipart/mixed and continue adding attachments, with the resulting MIME hierarchy:

  • multipart/mixed
    • multipart/related
      • multipart/alternative
        • text/html
        • text/plain
      • image/png
    • application/pdf

Solution 3

A nod to James on this, as it helped me get our mailer working right.

A slight refinement to this: First, we use the block arguments within the blocks to add parts (I had problems when I didn't).

Also, if you want to use layouts, you have to use #render directly. Here's an example of both principles at work. As shown above, you need to make sure you keep the html part last.

  def message_with_attachment_and_layout( options )
    from options[:from]
    recipients options[:to]
    subject options[:subject]
    content_type    "multipart/mixed"
    part :content_type => 'multipart/alternative' do |copy|
      copy.part :content_type => 'text/plain' do |plain|
        plain.body = render( :file => "#{options[:render]}.text.plain", 
          :layout => 'email', :body => options )
      end
      copy.part :content_type => 'text/html' do |html|
        html.body = render( :file => "#{options[:render]}.text.html", 
          :layout => 'email', :body => options )
      end
    end
    attachment :content_type => "application/pdf", 
      :filename => options[:attachment][:filename],
      :body => File.read( options[:attachment][:path] + '.pdf' )
  end

This example uses an options hash to create a generic multipart message with both attachments and layout, which you would use like this:

TestMailer.deliver_message_with_attachment_and_layout( 
  :from => '[email protected]', :to => '[email protected]', 
  :subject => 'test', :render => 'test', 
  :attachment => { :filename => 'A Nice PDF', 
    :path => 'path/to/some/nice/pdf' } )

(We don't actually do this: it's nicer to have each mailer fill in a lot of these details for you, but I thought it would make it easier to understand the code.)

Hope that helps. Best of luck.

Regards, Dan

Solution 4

Rails 3 handles mail differently--and while the simple case is easier, adding the correct MIME hierarchy for multipart email with both alternative content types and (inline) attachments is rather complicated (primarily because the hierarchy needed is so complex.)

Phil's answer will seem to work--but the attachments won't be visible on the iPhone (and perhaps other devices) since the MIME hierarchy is still incorrect.

The correct MIME hierarchy ends up looking like this:

  • multipart/mixed
    • multipart/alternative
      • multipart/related
        • text/html
        • image/png (e.g. for an inline attachment; pdf would be another good example)
      • text/plain
    • application/zip (e.g for an attachment--not inline)

I've released a gem that helps support the correct hierarchy: https://github.com/jcoleman/mail_alternatives_with_attachments

Typically when using ActionMailer 3, you would create a message with the following code:

class MyEmailerClass < ActionMailer::Base
  def my_email_method(address)
    mail :to => address, 
         :from => "[email protected]",
         :subject => "My Subject"
  end
end

Using this gem to create an email with both alternatives and attachments you would use the following code:

class MyEmailerClass < ActionMailer::Base
  def my_email_method(address, attachment, logo)
    message = prepare_message to: address, subject: "My Subject", :content_type => "multipart/mixed"

    message.alternative_content_types_with_attachment(
      :text => render_to_string(:template => "my_template.text"),
      :html => render_to_string("my_template.html")
    ) do |inline_attachments|
      inline_attachments.inline['logo.png'] = logo
    end

    attachments['attachment.pdf'] = attachment

    message
  end
end

Solution 5

Rails 3 Solution, multipart alternative email (html and plain) with pdf attachment, no inline attachments

Previously I had emails showing only the pdf attachment and neither plain nor html in the body when they were opened in ios or in mail.app on osx. Gmail has never been a problem.

I used the same solution as Corin, though I didn't need the inline attachment. That got me pretty far - except for one problem - mail.app / iOS mail showed the plain text not the html. This was (if finally transpired) because of the order in which the alternative parts came through, html first and then text (why that should be decisive beats me, but anyway).

So I had to make one more change, rather silly, but it works. add the .reverse! method.

so I have

def guest_notification(requirement, message)
 subject     = "Further booking details"
 @booking = requirement.booking
 @message = message

 mixed = mail(:to => [requirement.booking.email], :subject => subject) do |format|
   format.text
   format.html
 end

 mixed.add_part(
  Mail::Part.new do
   content_type 'multipart/alternative'
   # THE ODD BIT vv
   mixed.parts.reverse!.delete_if {|p| add_part p }
  end
 )

 mixed.content_type 'multipart/mixed'
 mixed.header['content-type'].parameters[:boundary] = mixed.body.boundary
 attachments['Final_Details.pdf'] = File.read(Rails.root + "public/FinalDetails.pdf")

end
Share:
19,107
Pieter Jongsma
Author by

Pieter Jongsma

Updated on June 04, 2022

Comments

  • Pieter Jongsma
    Pieter Jongsma almost 2 years

    I'm using the following code to send emails in rails:

    class InvoiceMailer < ActionMailer::Base
    
      def invoice(invoice)
        from          CONFIG[:email]
        recipients    invoice.email
        subject       "Bevestiging Inschrijving #{invoice.course.name}"
        content_type  "multipart/alternative"
    
        part "text/html" do |p|
          p.body = render_message 'invoice_html', :invoice => invoice
        end
    
        part "text/plain" do |p|
          p.body = render_message 'invoice_plain', :invoice => invoice
        end
    
        pdf = Prawn::Document.new(:page_size => 'A4')
        PDFRenderer.render_invoice(pdf, invoice)
        attachment :content_type => "application/pdf", :body => pdf.render, :filename => "factuur.pdf"
    
        invoice.course.course_files.each do |file|
          attachment :content_type => file.content_type, :body => File.read(file.full_path), :filename => file.filename
        end
      end
    
    end
    

    It seems fine to me, and the emails also show up like they should in the Gmail web-interface. In Mail (the Apple program), however, I get just 1 attachment (where there should be 2) and there is no text. I just can't seem to figure out what's causing it.

    I copied the email from the logs:

    
    Sent mail to [email protected]
    
    From: [email protected]
    To: [email protected]
    Subject: Bevestiging Inschrijving Authentiek Spreken
    Mime-Version: 1.0
    Content-Type: multipart/alternative; boundary=mimepart_4a5b035ea0d4_769515bbca0ce9b412a
    
    
    --mimepart_4a5b035ea0d4_769515bbca0ce9b412a
    Content-Type: text/html; charset=utf-8
    Content-Transfer-Encoding: Quoted-printable
    Content-Disposition: inline
    
    
    
      
      
      
        

    Dear sir

    = --mimepart_4a5b035ea0d4_769515bbca0ce9b412a Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: Quoted-printable Content-Disposition: inline Dear sir * Foo= --mimepart_4a5b035ea0d4_769515bbca0ce9b412a Content-Type: application/pdf; name=factuur.pdf Content-Transfer-Encoding: Base64 Content-Disposition: attachment; filename=factuur.pdf JVBERi0xLjMK/////woxIDAgb2JqCjw8IC9DcmVhdG9yIChQcmF3bikKL1By b2R1Y2VyIChQcmF3bikKPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0NvdW50IDEK ... ... ... MCBuIAp0cmFpbGVyCjw8IC9JbmZvIDEgMCBSCi9TaXplIDExCi9Sb290IDMg MCBSCj4+CnN0YXJ0eHJlZgo4Nzc1CiUlRU9GCg== --mimepart_4a5b035ea0d4_769515bbca0ce9b412a Content-Type: application/pdf; name=Spelregels.pdf Content-Transfer-Encoding: Base64 Content-Disposition: attachment; filename=Spelregels.pdf JVBERi0xLjQNJeLjz9MNCjYgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgMjEx NjYvTyA4L0UgMTY5NTIvTiAxL1QgMjEwMDAvSCBbIDg3NiAxOTJdPj4NZW5k ... ... ... MDIwNzQ4IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNj4+DQpzdGFydHhy ZWYNCjExNg0KJSVFT0YNCg== --mimepart_4a5b035ea0d4_769515bbca0ce9b412a--