代码之家  ›  专栏  ›  技术社区  ›  Pieter Jongsma

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

  •  24
  • Pieter Jongsma  · 技术社区  · 17 年前

    我使用以下代码在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 回复  |  直到 16 年前
        1
  •  23
  •   A.K.    6 年前

    我怀疑问题在于您将整个电子邮件定义为多部分/备选方案,表明每个部分只是同一邮件的备选视图。

    我使用以下类似的方法来发送带有附件的混合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|
          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
    
        2
  •  17
  •   Adriaan Koster    12 年前

    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 我们创建了一个新的 多部分/相关 零件,并添加现有零件的子零件(在执行时将其删除)。然后我们强制 Content-Type 成为 multipart/mixed 并继续添加附件,生成MIME层次结构:

    • 多部分/混合
      • 多部分/相关
        • 多部分/备选方案
          • 文本/html
          • 文本/纯文本
        • 图像/png
      • application/pdf
        3
  •  13
  •   Dan Yoder    16 年前

    对此稍加改进:首先,我们使用块中的块参数来添加部件(我没有添加部件时遇到问题)。

    此外,如果要使用布局,必须使用#直接渲染。这是两个原则都起作用的一个例子。如上所示,您需要确保最后保留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
  •  12
  •   jcoleman    14 年前

    Rails 3以不同的方式处理邮件——虽然简单的情况更容易,但为包含可选内容类型和(内联)附件的多部分电子邮件添加正确的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
    
        5
  •  6
  •   user1581404    13 年前

    Rails 3解决方案,多部分备选电子邮件(html和普通),带有pdf附件,无内联附件

    以前,当我在ios或osx上的mail.app中打开邮件时,我的邮件中只显示pdf附件,而在正文中既没有普通邮件也没有html。Gmail从来都不是问题。

    我使用了与Corin相同的解决方案,尽管我不需要内联附件。这让我走了很远——除了一个问题——mail.app/iOS邮件显示的是纯文本而不是html。这是(如果最终实现的话)因为可选部分的顺序,先是html,然后是文本(为什么这应该是决定性的,但无论如何,我都不知道)。

    所以我不得不再做一次改变,这很愚蠢,但它奏效了。加上这个。相反!方法

    所以我有

    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
    
        6
  •  5
  •   Phil Calvin    14 年前

    有关完整的解决方案,请参见下面jcoleman的答案。

    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 在问题中提到的三个备选方案问题之后添加附件。

        7
  •  4
  •   Yvo    10 年前

    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
    
        8
  •  3
  •   Kitebuggy    12 年前

    N.B.Rails 3.2解决方案。

    就像这些多部分电子邮件一样,这个答案有多个部分,因此我将相应地进行剖析:

    1. @Corin的“混合”方法中的plain/html格式的顺序很重要。我发现html后面的文本提供了我所需要的功能。YMMV

    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