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

Pie*_*sma 24 email ruby-on-rails actionmailer

我正在使用以下代码在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
Run Code Online (Sandbox Code Playgroud)

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

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


Sent mail to xxx@gmail.com

From: yyy@gmail.com
To: xxx@gmail.com
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--

Jam*_*aly 21

我怀疑问题是你将整个电子邮件定义为multipart/alternative,建议每个部分只是同一消息的备用视图.

我使用类似下面的内容发送带附件的混合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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,投票最多的答案适用于Rails 2.x,并且有几个答案涉及Rails 3(@jcoleman,@ Coin).我第一次扫描这个帖子时,我错过了Rails 3的答案并继续前进,然后重新阅读了这个帖子,让一切都完美无缺. (2认同)

Cor*_*rin 15

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

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
Run Code Online (Sandbox Code Playgroud)

此代码首先创建以下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


小智 12

向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
Run Code Online (Sandbox Code Playgroud)

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

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' } )
Run Code Online (Sandbox Code Playgroud)

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

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

问候,丹


jco*_*man 12

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
Run Code Online (Sandbox Code Playgroud)

使用此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
Run Code Online (Sandbox Code Playgroud)


小智 6

Rails 3 解决方案,带有 pdf 附件的多部分替代电子邮件(html 和纯文本),无内嵌附件

以前,当我在 ios 或 osx 上的 mail.app 中打开电子邮件时,我的电子邮件仅显示 pdf 附件,正文中既不显示普通也不显示 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
Run Code Online (Sandbox Code Playgroud)


Phi*_*vin 5

注意:我的技术在某些情况下有效.但是,当与内嵌图像结合使用时,会导致附件无法在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
Run Code Online (Sandbox Code Playgroud)

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


Zyp*_*rax 5

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
Run Code Online (Sandbox Code Playgroud)