Проблема отправки многочастной почты с помощью ActionMailer - PullRequest
24 голосов
/ 13 июля 2009

Я использую следующий код для отправки писем по рельсам:

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



  
  
  
    <p>Dear sir</p>
  
=

--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 ]

21 голосов
/ 15 июля 2009

Я подозреваю, что проблема заключается в том, что вы определяете общее электронное письмо как составное / альтернативное, предполагая, что каждая часть является просто альтернативным представлением того же сообщения.

Я использую что-то вроде следующего для отправки смешанных электронных писем в формате 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|
      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
15 голосов
/ 03 июля 2012

@ 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-Type равным multipart/mixed и продолжаем добавлять вложения с полученной иерархией MIME:

  • multipart/mixed
    • multipart/related
      • multipart/alternative
        • text/html
        • text/plain
      • image/png
    • application/pdf
12 голосов
/ 24 декабря 2011

Rails 3 обрабатывает почту по-разному - и хотя простой случай проще, добавление правильной иерархии MIME для составной электронной почты с альтернативными типами контента и (встроенными) вложениями довольно сложно (в первую очередь потому, что необходимая иерархия настолько сложна)

Может показаться, что ответ Фила работает, но вложения не будут видны на iPhone (и, возможно, на других устройствах), поскольку иерархия MIME все еще неверна.

Правильная иерархия MIME выглядит примерно так:

  • multipart/mixed
    • multipart/alternative
      • multipart/related
        • text/html
        • image/png (например, для встроенного вложения; другой хороший пример - pdf)
      • text/plain
    • application/zip (например, для вложения - не встроено)

Я выпустил гем, который помогает поддерживать правильную иерархию: 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

Используя этот гем для создания электронного письма с альтернативами и вложениями, вы должны использовать следующий код:

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 голосов
/ 04 сентября 2009

Это намек Джеймсу, так как это помогло мне правильно настроить почтовую программу.

Небольшое уточнение: во-первых, мы используем аргументы блока внутри блоков для добавления деталей (у меня были проблемы, когда я этого не делал).

Также, если вы хотите использовать макеты, вы должны использовать #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' } )

(На самом деле мы этого не делаем: лучше, чтобы каждый почтовик заполнил для вас множество этих деталей, но я подумал, что это облегчит понимание кода.)

Надеюсь, это поможет. Желаем удачи.

С уважением, Dan

5 голосов
/ 24 августа 2011

Примечание: моя методика ниже работает в некоторых случаях. Однако в сочетании с встроенными изображениями это приведет к тому, что вложение не будет отображаться на iPhone Mail и, возможно, на других клиентах. См. Ответ 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 - добавление вложения после запуска проблемы трех альтернатив, упомянутой в вопросе.

4 голосов
/ 02 мая 2013

Решение Rails 3, альтернативная электронная почта из нескольких частей (html и обычный) с вложением в формате PDF, без вложенных вложений

Раньше у меня были электронные письма, показывающие только вложение в формате pdf и ни обычного, ни HTML в теле, когда они были открыты в ios или в mail.app на osx. Gmail никогда не был проблемой.

Я использовал то же решение, что и Корин, хотя мне не требовалось встроенное вложение. Это дало мне довольно далеко - за исключением одной проблемы - почта 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
3 голосов
/ 22 апреля 2016

Рельсы 4 решения

В нашем проекте мы отправляем клиенту электронные письма с логотипом компании (встроенное вложение) и PDF (обычное вложение). Обходной путь, который у нас был, был похож на тот, что предоставлен здесь @ user1581404.

Однако после обновления проекта до Rails 4 нам пришлось искать новое решение, потому что вы больше не можете добавлять вложения после вызова команды mail.

У наших почтовиков есть базовый почтовик, мы исправляем эту проблему, переопределяя метод 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
2 голосов
/ 31 декабря 2013

N.B. Решение Rails 3.2.

Так же, как эти составные электронные письма, этот ответ состоит из нескольких частей, поэтому я расскажу соответственно:

  1. Порядок простого / html формата в «смешанном» подходе @ Корина важен. Я обнаружил, что текст, после которого HTML, дал мне необходимую функциональность. YMMV

  2. Установка Content-Disposition на ноль (чтобы удалить его) исправила трудности просмотра вложений iPhone / iOS, выраженные в других ответах. Это решение было протестировано как работающее для Outlook для 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
...