使用ActionMailer发送多部分邮件时出现问题

我正在使用以下代码在rails中发送电子邮件:

class InvoiceMailer  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 

对我来说似乎很好,电子邮件也会在Gmail网络界面中显示出来。 但是,在Mail(Apple程序)中,我只得到1个附件(应该有2个附件)并且没有文本。 我似乎无法弄清楚是什么导致了它。

我从日志中复制了电子邮件:


已发送邮件至xxx@gmail.com

来自:yyy@gmail.com
致:xxx@gmail.com
主题:Insvestiging Inschrijving Authentiek Spreken
哑剧版:1.0
内容类型:multipart / alternative; 边界= mimepart_4a5b035ea0d4_769515bbca0ce9b412a


 --mimepart_4a5b035ea0d4_769515bbca0ce9b412a
 Content-Type:text / html; 字符集= utf-8的
 Content-Transfer-Encoding:引用可打印
内容 - 处置:内联



  
  
  
    

亲爱的先生

= --mimepart_4a5b035ea0d4_769515bbca0ce9b412a Content-Type:text / plain; 字符集= utf-8的 Content-Transfer-Encoding:引用可打印 内容 - 处置:内联 亲爱的先生 * Foo = --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:application / pdf; 名称= factuur.pdf 内容传输编码:Base64 内容 - 处理:附件; 文件名= factuur.pdf JVBERi0xLjMK ///// woxIDAgb2JqCjw8IC9DcmVhdG9yIChQcmF3bikKL1By b2R1Y2VyIChQcmF3bikKPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0NvdW50IDEK ...... ...... MCBuIAp0cmFpbGVyCjw8IC9JbmZvIDEgMCBSCi9TaXplIDExCi9Sb290IDMg MCBSCj4 + CnN0YXJ0eHJlZgo4Nzc1CiUlRU9GCg == --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:application / pdf; 名称= Spelregels.pdf 内容传输编码:Base64 内容 - 处理:附件; 文件名= Spelregels.pdf JVBERi0xLjQNJeLjz9MNCjYgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgMjEx NjYvTyA4L0UgMTY5NTIvTiAxL1QgMjEwMDAvSCBbIDg3NiAxOTJdPj4NZW5k ...... ...... MDIwNzQ4IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNj4 + DQpzdGFydHhy ZWYNCjExNg0KJSVFT0YNCg == --mimepart_4a5b035ea0d4_769515bbca0ce9b412a--

我怀疑问题在于您将整个电子邮件定义为多部分/替代,建议每个部分只是同一消息的备用视图。

我使用类似下面的内容发送带附件的混合html /普通电子邮件,它似乎工作正常。

 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| 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 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 

@jcoleman是正确的,但如果你不想使用他的gem,那么这可能是一个更好的解决方案:

 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 

此代码首先创建以下MIME层次结构:

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

在调用mail之后,我们创建一个新的multipart/related部件并添加现有部件的子部件(随时移除它们)。 然后我们强制Content-Typemultipart/mixed并继续添加附件,以及生成的MIME层次结构:

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

向James致敬,因为它帮助我让我们的邮件正常工作。

稍微改进一下:首先,我们使用块中的块参数来添加部分(当我没有时,我遇到了问题)。

此外,如果要使用布局,则必须直接使用#render。 这是两个原则在工作中的一个例子。 如上所示,您需要确保最后保留html部分。

  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 

此示例使用选项哈希创建包含附件和布局的通用多部分消息,您可以像这样使用:

 TestMailer.deliver_message_with_attachment_and_layout( :from => 'a@fubar.com', :to => 'b@fubar.com', :subject => 'test', :render => 'test', :attachment => { :filename => 'A Nice PDF', :path => 'path/to/some/nice/pdf' } ) 

(我们实际上并没有这样做:让每个邮件程序为您填写很多这些详细信息更好,但我认为这样可以更容易理解代码。)

希望有所帮助。 祝你好运。

问候,丹

Rails 3以不同的方式处理邮件 – 虽然简单的情况更容易,但是为多部分电子邮件添加正确的MIME层次结构,其中包含备用内容类型和(内联)附件相当复杂(主要是因为所需的层次结构非常复杂。)

Phil的答案似乎有效 – 但由于MIME层次结构仍然不正确,因此iPhone(以及可能还有其他设备)上不会显示附件。

正确的MIME层次结构最终看起来像这样:

  • multipart/mixed
    • multipart/alternative
      • multipart/related
        • text/html
        • image/png (例如,对于内联附件; pdf将是另一个很好的例子)
      • text/plain
    • application/zip (例如附件 – 不是内联的)

我发布了一个有助于支持正确层次结构的gem: https : //github.com/jcoleman/mail_alternatives_with_attachments

通常,在使用ActionMailer 3时,您将使用以下代码创建消息:

 class MyEmailerClass < ActionMailer::Base def my_email_method(address) mail :to => address, :from => "noreply@myemail.com", :subject => "My Subject" end end 

使用此gem创建包含备选方案和附件的电子邮件,您将使用以下代码:

 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 

注意:我的技术在某些情况下适用。 但是,与内联图像结合使用时,会导致附件无法在iPhone Mail上显示,也可能在其他客户端上显示。 有关完整的解决方案,请参阅下面的jcoleman答案。

值得注意的是,Rails现在处理这个问题,至少从3.1rc4开始 。 从ActionMailer指南:

 class UserMailer < ActionMailer::Base def welcome_email(user) @user = user @url = user_url(@user) attachments['terms.pdf'] = File.read('/path/terms.pdf') mail(:to => user.email, :subject => "Please see the Terms and Conditions attached") end end 

诀窍是在调用mail 之前添加附件 – 在触发问题中提到的三个替代问题后添加附件。

Rails 3解决方案,多部分替代电子邮件(html和plain),带有pdf附件,没有内联附件

以前我的电子邮件只显示pdf附件,当它们在ios中打开或在osx上的mail.app中打开时,身体既不是普通也不是html。 Gmail从来都不是问题。

我使用与Corin相同的解决方案,但我不需要内联附件。 这让我相当远 – 除了一个问题 – mail.app / iOS邮件显示纯文本而不是html。 这是(如果最终发生)因为替代部分的顺序,首先是html然后是文本(为什么这应该是决定性的打败我,但无论如何)。

所以我不得不再做一次改变,相当愚蠢,但它确实有效。 添加.reverse! 方法。

所以我有

 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 

Rails 4解决方案

在我们的项目中,我们向客户发送包含公司徽标(内联附件)和PDF(常规附件)的电子邮件。 我们采用的解决方法类似于@ user1581404提供的解决方法。

但是,在将项目升级到Rails 4后,我们必须找到一个新的解决方案,因为在调用mail命令后不再允许您添加附件。

我们的邮件程序有一个基本邮件程序,我们通过覆盖邮件方法修复此问题:

 def mail(headers = {}, &block) message = super # If there are no regular attachments, we don't have to modify the mail return message unless message.parts.any? { |part| part.attachment? && !part.inline? } # Combine the html part and inline attachments to prevent issues with clients like iOS html_part = Mail::Part.new do content_type 'multipart/related' message.parts.delete_if { |part| (!part.attachment? || part.inline?) && add_part(part) } end # Any parts left must be regular attachments attachment_parts = message.parts.slice!(0..-1) # Reconfigure the message message.content_type 'multipart/mixed' message.header['content-type'].parameters[:boundary] = message.body.boundary message.add_part(html_part) attachment_parts.each { |part| message.add_part(part) } message end 

NB Rails 3.2解决方案。

就像这些多部分电子邮件一样,这个答案有很多部分,所以我会相应地进行剖析:

  1. @ Corin的“混合”方法中的plain / html格式的顺序很重要。 我发现html后面的文字给了我所需的function。 因人而异

  2. 将Content-Disposition设置为nil(删除它)修复了iPhone / iOS附件查看其他答案中表达的难点。 此解决方案已经过测试,适用于Outlook for Mac,Mac OS / X Mail和iOS Mail。 我怀疑其他电子邮件客户端也可以正常工作。

  3. 与以前版本的Rails不同,附件处理与广告一样有效。 我最大的问题通常是尝试旧的解决方法,这些解决方案只能为我解决问题。

希望这可以帮助别人避免我的陷阱和死胡同。

工作代码:

 def example( from_user, quote) @quote = quote # attach the inline logo attachments.inline['logo.png'] = File.read('./public/images/logo.png') # attach the pdf quote attachments[ 'quote.pdf'] = File.read( 'path/quote.pdf') # create a mixed format email body mixed = mail( to: @quote.user.email, subject: "Quote") do |format| format.text format.html end # Set the message content-type to be 'multipart/mixed' mixed.content_type 'multipart/mixed' mixed.header['content-type'].parameters[:boundary] = mixed.body.boundary # Set Content-Disposition to nil to remove it - fixes iOS attachment viewing mixed.content_disposition = nil end