使用 ActionMailer 发送多部分邮件时出现问题
Posted
技术标签:
【中文标题】使用 ActionMailer 发送多部分邮件时出现问题【英文标题】:Problem sending multipart mail using ActionMailer 【发布时间】:2010-11-10 06:07:07 【问题描述】:我正在使用以下代码在 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 个)并且没有文本。我似乎无法弄清楚是什么原因造成的。
我从日志中复制了电子邮件:
发送邮件至 xxx@gmail.com 发件人:yyy@gmail.com 至:xxx@gmail.com 主题:Bevestiging Inschrijving Authentiek Spreken 哑剧版:1.0 内容类型:多部分/替代;边界=mimepart_4a5b035ea0d4_769515bbca0ce9b412a --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:文本/html;字符集=utf-8 内容传输编码:引用打印 内容处置:内联尊敬的先生
= --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:文本/纯文本;字符集=utf-8 内容传输编码:引用打印 内容处置:内联 尊敬的先生 * 富= --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:应用程序/pdf;名称=factuur.pdf 内容传输编码:Base64 内容处置:附件;文件名=factuur.pdf JVBERi0xLjMK/////woxIDAgb2JqCjw8IC9DcmVhdG9yIChQcmF3bikKL1By b2R1Y2VyIChQcmF3bikKPj4KZW5kb2JqCjIgMCBvYmoKPDwgL0NvdW50IDEK ………… MCBuIAp0cmFpbGVyCjw8IC9JbmZvIDEgMCBSCi9TaXplIDExCi9Sb290IDMg MCBSCj4+CnN0YXJ0eHJlZgo4Nzc1CiUlRU9GCg== --mimepart_4a5b035ea0d4_769515bbca0ce9b412a 内容类型:应用程序/pdf;名称=Spelregels.pdf 内容传输编码:Base64 内容处置:附件;文件名=Spelregels.pdf JVBERi0xLjQNJeLjz9MNCjYgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgMjEx NjYvTyA4L0UgMTY5NTIvTiAxL1QgMjEwMDAvSCBbIDg3NiAxOTJdPj4NZW5k ………… MDIwNzQ4IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNj4+DQpzdGFydHhy ZWYNCjExNg0KJSVFT0YNCg== --mimepart_4a5b035ea0d4_769515bbca0ce9b412a--【问题讨论】:
【参考方案1】:我怀疑问题在于您将整个电子邮件定义为多部分/替代,这表明每个部分只是同一消息的替代视图。
我使用类似下面的东西来发送带有附件的混合 html/plain 电子邮件,它似乎工作正常。
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
【讨论】:
请注意,投票最多的答案适用于 Rails 2.x,并且有几个答案适用于 Rails 3(@jcoleman,@Corin)。第一次扫描此线程时,我错过了 Rails 3 的答案并继续前进,然后重新阅读线程并让一切正常运行。 在上面的代码中,我认为它需要是p.part "text/html" 和p.part "text/plain",而不仅仅是part "text/html" ...跨度> 【参考方案2】:@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-Type
强制为multipart/mixed
并继续添加附件,生成的MIME 层次结构:
multipart/mixed
multipart/related
multipart/alternative
text/html
text/plain
image/png
application/pdf
【讨论】:
谢谢!经过2个小时的各种尝试,终于奏效了。请投票赞成这个答案,以便它与 Rails 2.x 一起看到。【参考方案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' )
(我们实际上并没有这样做:让每个邮件程序为您填写很多这些细节会更好,但我认为这会更容易理解代码。)
希望对您有所帮助。祝你好运。
问候, 丹
【讨论】:
谢谢。您的回答和Ruby Guide 帮助我解决了多部分电子邮件和附件的类似问题。【参考方案4】:Rails 3 处理邮件的方式不同——虽然简单的情况更容易,但为具有替代内容类型和(内联)附件的多部分电子邮件添加正确的 MIME 层次结构相当复杂(主要是因为所需的层次结构非常复杂。)
Phil 的回答似乎有效,但附件在 iPhone(可能还有其他设备)上不可见,因为 MIME 层次结构仍然不正确。
正确的 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
【讨论】:
我检查了@jcoleman 的gem,但我不确定我是否遗漏了什么......如果你在加载内联附件之前呈现html 字符串,你如何引用他们的CID ?如果 my_template.html 仅将 logo.png 引用为图像源,则生成的电子邮件在多个客户端(例如 Hotmail)上呈现良好,但在其他客户端(iPhone、Apple Mail)上呈现不佳,因为这些客户端使用内联附件的 CID。这里有什么提示吗? @JuanCarlosMéndez:我没有遇到这种需要(我假设这里的区别是您在 HTML 中使用内联附件,而我设计它时仅考虑是否默认情况下它是内联显示的。)我欢迎在 GitHub 上的项目上提出任何拉取请求。【参考方案5】: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
【讨论】:
这是最好的答案。【参考方案6】:注意:我下面的技术在某些情况下有效。但是,当与内联图像结合使用时,它会导致附件无法显示在 iPhone 邮件上,并且可能不会显示在其他客户端上。请参阅下面 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
之前添加附件—在触发问题中提到的三种选择问题之后添加附件。
【讨论】:
要添加的一件事是确保您的视图文件被命名为 *.text.erb 和 *.text.html.erb(如果您有 html 内容),例如 Phil 提供, ActionMailer 会让事情自动发生【参考方案7】: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】:注意Rails 3.2 解决方案。
就像这些多部分电子邮件一样,这个答案有多个部分,所以我会相应地剖析:
@Corin 的“混合”方法中纯/html 格式的顺序很重要。我发现后跟 html 的文本给了我所需的功能。 YMMV
将 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
【讨论】:
以上是关于使用 ActionMailer 发送多部分邮件时出现问题的主要内容,如果未能解决你的问题,请参考以下文章
使用 ActionMailer 和 Outlook/Mandrillapp SMTP 服务器发送邮件
在 Rails3 / ActionMailer 中设置 Message-ID 邮件头