Подтвердить что ты не робот

Проблема отправки многостраничной почты с помощью ActionMailer

Я использую следующий код для отправки писем в 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 [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--
4b9b3361

Ответ 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|
      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

Ответ 2

@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

Ответ 3

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

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

Кроме того, если вы хотите использовать макеты, вам нужно напрямую использовать #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 => '[email protected]', :to => '[email protected]', 
  :subject => 'test', :render => 'test', 
  :attachment => { :filename => 'A Nice PDF', 
    :path => 'path/to/some/nice/pdf' } )

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

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

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

Ответ 4

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 => "[email protected]",
         :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

Ответ 5

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

Ответ 6

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

Раньше у меня были электронные письма, показывающие только вложение в формате pdf и ни простой, ни html в теле, когда они были открыты в ios или mail.app в osx. 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

Ответ 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

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

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

  • Порядок простого/html-формата в "смешанном" подходе @Corin важен. Я нашел текст, за которым следует html, дал мне необходимую мне функциональность. YMMV

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