带有 ical 附件的多部分电子邮件显示不正确

Multipart email with ical attachment displays incorrectly

提问人:Chiperific 提问时间:9/30/2022 更新时间:10/1/2022 访问量:169

问:

我不确定这是否是我的代码、ActionMailerMail 甚至 icalendar gem 的问题?

用户注册一个活动,他们会收到一封带有 ical 附件的电子邮件:

# app/mailers/registration_mailer.rb

class RegistrationMailer < ApplicationMailer
  helper MailerHelper

  def created(registration)
    ...

    cal = Icalendar::Calendar.new
    cal.event do |e|
      e.dtstart = @event.start_time
      e.dtend = @event.end_time
      e.organizer = 'mailto:[email protected]'
      e.attendee = @recipient
      e.location = @location.addr_one_liner
      e.summary = @summary
      e.description = @description
    end
    cal.append_custom_property('METHOD', 'REQUEST')
    mail.attachments[@attachment_title] = { mime_type: 'text/calendar', content: cal.to_ical }

    mail(to: @recipient.email, subject: "[20 Liters] You registered for a filter build on #{@event.mailer_time}")
  end

  ...
end

我有文本和 HTML 视图:

  • app/views/registration_mailer/created.text.erb
  • app/views/registration_mailer/created.html.erb

当我省略附件时,电子邮件的结构如下:

Header stuff...
Mime-Version: 1.0
Content-Type: multipart/alternative; boundary="--==_mimepart_63358693571_1146901122e"; charset=UTF-8
Content-Transfer-Encoding: 7bit

----==_mimepart_63358693571_1146901122e
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the text version of the email here]

----==_mimepart_63358693571_1146901122e
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the HTML version of the email here]
----==_mimepart_63358693571_1146901122e--

当附件存在时,电子邮件的结构如下:

Header stuff...
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="--==_mimepart_6335c3388b140_114924286ed"; charset=UTF-8
Content-Transfer-Encoding: 7bit

----==_mimepart_6335c3388b140_114924286ed
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the text version of the email here]

----==_mimepart_6335c3388b140_114924286ed
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the HTML version of the email here]

----==_mimepart_6335c3388b140_114924286ed
Content-Type: multipart/alternative; boundary="--==_mimepart_6335c3389bc30_114924287a3"; charset=UTF-8
Content-Transfer-Encoding: 7bit

----==_mimepart_6335c3389bc30_114924287a3
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the text version of the email AGAIN]

----==_mimepart_6335c3389bc30_114924287a3
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 7bit

[the HTML version of the email AGAIN]

----==_mimepart_6335c3389bc30_114924287a3--
----==_mimepart_6335c3388b140_114924286ed
Content-Type: text/calendar; charset=UTF-8
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=20Liters_filterbuild_20221011T0900.ical
Content-ID: <[email protected]>

[numbers and letters]
----==_mimepart_6335c3388b140_114924286ed--

突然变成了一棵奇怪的树:

1. Content-Type: multipart/mixed
    A. Content-Type: text/plain
    B. Content-Type: text/html
    C. Content-Type: multipart/alternative
        i. Content-Type: text/plain
        ii. Content-Type: text/html
    D. Content-Type: text/calendar

Rails 的邮件预览不会重现这个问题,也没有使用 Litmus 的电子邮件客户端预览(因为它似乎删除了文本部分和附件),但我假设内容类型的变形结构这不仅仅是特定于客户端的渲染问题。

我认为这来自 ActionMailer 下面的 Mail gem,它奇怪地构建了内容类型,但我在这里有点超出了我的深度。可能是 ActionMailer,我真的不知道怎么说。

我不是很精通这个,但我想我想要这个结构:

1. Content-Type: multipart/mixed
    A. Content-Type: multipart/alternative
        i. Content-Type: text/plain
        ii. Content-Type: text/html
    B. Content-Type: text/calendar

所以,两个问题:

1. 如果这是我的代码,我做错了什么?

2. 如果它不是我的代码,我可以强制使用我想要的结构吗?

我一直在梳理 ActionMailer 和 Mail 代码库,但还没有找到一种方法来手动将我的电子邮件形成到这个级别。

Ruby-on-Rails 电子邮件 iCalendar Actionmailer Ruby-on-Rails-6.1

评论


答:

0赞 Chiperific 10/1/2022 #1

经过更多的挖掘,我责怪 ActionMailer,尽管我仍然不确定为什么文本和 html 部分被添加两次。

我特定用途的一个猴子补丁是让 ActionMailer 和 Mail 构建对象,然后手动删除不需要的部分:mail

# app/mailers/registration_mailer.rb

...

mail.attachments[@attachment_title] = { mime_type: 'text/calendar', content: cal.to_ical }

mail(to: @recipient.email, subject: "[20 Liters] You registered for a filter build on #{@event.mailer_time}")

# PATCH: text and html parts are getting inserted in multipart/mixed (top level) as well as multipart/alternative (2nd level)
mail.parts.reject! { |part| !part.attachment? && !part.multipart? }

这仅适用于我的特定情况。如果为您创建的嵌套不同,则您的语句将需要不同。mailreject!

就我而言,构建此结构:mail

1. Content-Type: multipart/mixed
    A. Content-Type: text/plain
    B. Content-Type: text/html
    C. Content-Type: multipart/alternative
        i. Content-Type: text/plain
        ii. Content-Type: text/html
    D. Content-Type: text/calendar

因此,我进入第一级 (A. - D.) 并拒绝任何非多部分且非附件的部分:

1. Content-Type: multipart/mixed
    A. Content-Type: text/plain <-- not multipart, not attachment = rejected
    B. Content-Type: text/html <-- not multipart, not attachment = rejected
    C. Content-Type: multipart/alternative  <-- is multipart, not attachment = kept
        i. Content-Type: text/plain
        ii. Content-Type: text/html
    D. Content-Type: text/calendar  <-- not multipart, is attachment = kept

如果您遇到此问题,我建议您使用调试器来检查您的对象,特别关注 .请记住,可以深度嵌套。mailpartsparts

Mail::Part继承自 Mail::Message,它有一些有用的方法来确定消息的“形状”

  • multipart?
  • attachment?
  • attachments
  • has_attachments?
  • boundary
  • parts
  • all_parts

祝你好运。