Problem sending multipart mail using ActionMailer
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
Pieter Jongsma
Updated on June 04, 2022Comments
-
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--