ruby-on-rails - rails mailer
Problema al enviar correo de varias partes mediante ActionMailer (8)
Estoy usando el siguiente código para enviar correos electrónicos en rieles:
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
Me parece bien, y los correos electrónicos también aparecen como deberían en la interfaz web de Gmail. En Mail (el programa de Apple), sin embargo, obtengo solo 1 archivo adjunto (donde debería haber 2) y no hay texto. Simplemente no puedo entender lo que lo está causando.
Copié el correo electrónico de los registros:
Sent mail to [email protected] From: [email protected] To: [email protected] 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--@jcoleman es correcto, pero si no quieres usar su gema, esta puede ser una mejor solución:
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
Este código comienza creando la siguiente jerarquía MIME:
-
multipart/related
-
multipart/alternative
-
text/html
-
text/plain
-
-
image/png
-
Después de la llamada al mail
creamos una nueva parte multipart/related
y agregamos los elementos secundarios de la parte existente (eliminándolos a medida que avanzamos). A continuación, forzamos el tipo de Content-Type
para que sea multipart/mixed
y continúe agregando archivos adjuntos, con la jerarquía MIME resultante:
-
multipart/mixed
-
multipart/related
-
multipart/alternative
-
text/html
-
text/plain
-
-
image/png
-
-
application/pdf
-
Rails 3 maneja el correo de manera diferente, y aunque el caso simple es más fácil, agregar la jerarquía MIME correcta para correo electrónico de varias partes con ambos tipos de contenido alternativo y archivos adjuntos (en línea) es bastante complicado (principalmente porque la jerarquía necesaria es muy compleja).
La respuesta de Phil parecerá funcionar, pero los archivos adjuntos no serán visibles en el iPhone (y tal vez en otros dispositivos) ya que la jerarquía MIME sigue siendo incorrecta.
La jerarquía MIME correcta termina luciendo así:
-
multipart/mixed
-
multipart/alternative
-
multipart/related
-
text/html
-
image/png
(por ejemplo, para un archivo adjunto en línea, el pdf sería otro buen ejemplo)
-
-
text/plain
-
-
application/zip
(por ejemplo, para un archivo adjunto - no en línea)
-
Lancé una joya que ayuda a respaldar la jerarquía correcta: https://github.com/jcoleman/mail_alternatives_with_attachments
Normalmente, al utilizar ActionMailer 3, debe crear un mensaje con el siguiente código:
class MyEmailerClass < ActionMailer::Base
def my_email_method(address)
mail :to => address,
:from => "[email protected]",
:subject => "My Subject"
end
end
Usando esta joya para crear un correo electrónico con alternativas y archivos adjuntos, usaría el siguiente código:
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
Sospecho que el problema es que está definiendo el correo electrónico general como multipart / alternative, sugiriendo que cada parte sea solo una vista alternativa del mismo mensaje.
Utilizo algo como lo siguiente para enviar correos electrónicos mixto html / plain con archivos adjuntos, y parece funcionar bien.
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
Un guiño a James en esto, ya que me ayudó a hacer que nuestro correo funcione correctamente.
Un pequeño refinamiento a esto: Primero, usamos los argumentos del bloque dentro de los bloques para agregar partes (tuve problemas cuando no lo hice).
Además, si desea usar diseños, debe usar #render directamente. Aquí hay un ejemplo de ambos principios en acción. Como se muestra arriba, debe asegurarse de mantener la parte html al final.
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
Este ejemplo utiliza un hash de opciones para crear un mensaje multiparte genérico con archivos adjuntos y diseño, que usaría así:
TestMailer.deliver_message_with_attachment_and_layout(
:from => ''[email protected]'', :to => ''[email protected]'',
:subject => ''test'', :render => ''test'',
:attachment => { :filename => ''A Nice PDF'',
:path => ''path/to/some/nice/pdf'' } )
(En realidad, no hacemos esto: es mejor que cada correo complete muchos de estos detalles, pero pensé que sería más fácil entender el código).
Espero que ayude. La mejor de las suertes.
Saludos, Dan
NB Rails 3.2 solución.
Al igual que estos correos electrónicos de varias partes, esta respuesta tiene varias partes, por lo que analizaré en consecuencia:
El orden del formato plain / html en el enfoque "mixto" de @ Corin es importante. Encontré que el texto seguido de html me dio la funcionalidad que necesitaba. YMMV
Al configurar Content-Disposition en nil (para eliminarlo) se corrigieron las dificultades de visualización de archivos adjuntos de iPhone / iOS expresadas en otras respuestas. Se ha probado que esta solución funciona para Outlook para Mac, Mac OS / X Mail y iOS Mail. Sospecho que otros clientes de correo electrónico también funcionarían.
A diferencia de versiones anteriores de Rails, el manejo de los archivos adjuntos funcionaba como se anunciaba. Mis problemas más importantes solían ser causados por intentos de soluciones anteriores que solo agravaban los problemas para mí.
Espero que esto ayude a alguien a evitar mis trampas y callejones sin salida.
Código de trabajo:
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
Rails 3 Solution, correo electrónico alternativo de varias partes (html y simple) con archivo adjunto en pdf, sin archivos adjuntos en línea
Anteriormente, tenía correos electrónicos que mostraban solo el archivo adjunto en PDF y no eran simples ni html en el cuerpo cuando se abrían en ios o en mail.app en osx. Gmail nunca ha sido un problema.
Usé la misma solución que Corin, aunque no necesitaba el archivo adjunto en línea. Eso me llevó bastante lejos, excepto por un problema: el correo de mail.app / iOS mostraba el texto sin formato, no el html. Esto fue (si finalmente sucedió) debido al orden en que aparecieron las partes alternativas, html primero y luego texto (por qué eso debería ser decisivo), pero de todos modos.
Así que tuve que hacer un cambio más, bastante tonto, pero funciona. agrega el .reverse! método.
así que tengo
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
Nota: Mi técnica a continuación funciona en algunos casos. Sin embargo, cuando se combina con imágenes en línea, hará que el archivo adjunto no se muestre en el correo de iPhone, y tal vez en otros clientes. Vea la respuesta de jcoleman a continuación para una solución completa.
Vale la pena señalar que Rails ahora maneja esto, al menos a partir de 3.1rc4 . De la guía de 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
El truco consiste en agregar el archivo adjunto antes de realizar la llamada al mail
agregar el archivo adjunto después de desencadenar el problema de tres alternativas mencionado en la pregunta.
Solución Rails 4
En nuestro proyecto, enviamos correos electrónicos al cliente que incluyen un logotipo de la empresa (archivo adjunto en línea) y un PDF (archivo adjunto normal). La solución alternativa que teníamos en el lugar era similar a la proporcionada aquí por @ user1581404.
Sin embargo, después de actualizar el proyecto a Rails 4 tuvimos que encontrar una nueva solución porque ya no está permitido agregar archivos adjuntos después de llamar al comando de mail
.
Nuestros correos tienen un programa de correo base, solucionamos este problema anulando el método de correo con:
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