首页 文章

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

提问于
浏览
24

我正在使用以下代码在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

这对我来说似乎很好,电子邮件也会在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--

8 回答

  • 12

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

    我使用类似下面的内容发送带附件的混合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
    
  • 21

    @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
    

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

  • 3

    向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' } )
    

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

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

    问候,丹

  • 4

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

    Note: My technique below works in some instances. However, when combined with inline images, it will cause the attachment to not show on iPhone Mail, and perhaps on other clients. 有关完整的解决方案,请参阅下面的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
    

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

  • 15

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

    以前我的电子邮件只显示了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
    
  • 2

    Rails 4 solution

    在我们的项目中,我们向客户发送包含公司徽标(内联附件)和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
    
  • 5

    N.B. Rails 3.2 solution.

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

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

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

    • 与以前版本的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
    

相关问题