rails ruby-on-rails email actionmailer

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:

  1. 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

  2. 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.

  3. 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