ActiveCampaign/postmark-rails

Calendar Attachments

Closed this issue · 11 comments

I've been trying to send a calendar invite as an attachment to an email. I can get the calendar invite to attach and come through, but Outlook and other mail clients fail to recognise it as a calendar invite.

I've been trying to follow the steps you mention here, but I can't get the content type and other info to pass through.

attachments.inline['v2.ics'] = { mime_type: 'text/calendar', name: 'v2.ics', content_type: 'text/calendar; charset=utf-8; method=REQUEST', content: File.read(Rails.root + 'spec/fixtures/v2.ics') }

But the actual email has it attached as:

Content-Type: text/calendar; name=v2.ics
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=v2.ics

when I think what I want is something more like:

Content-Type: text/calendar; method=REQUEST
Content-Transfer-Encoding: Base64
Content-Disposition: inline; filename=v2.ics

The wiki implies the gem is compatible with the ActionMailer API, so what am I missing?

I'm using version 0.22.1 of the gem

Hi @rmaspero,

Looks like you are explicitly attaching the invite inline (attachments.inline bit) in your example:

attachments.inline['v2.ics'] = { 
  mime_type: 'text/calendar', 
  name: 'v2.ics', 
  content_type: 'text/calendar; charset=utf-8; method=REQUEST', 
  content: File.read(Rails.root + 'spec/fixtures/v2.ics') 
}

To get the Content-Disposition as attachment can you please try this:

attachments['v2.ics'] = { 
  mime_type: 'text/calendar', 
  name: 'v2.ics', 
  content_type: 'text/calendar; charset=utf-8; method=REQUEST', 
  content: File.read(Rails.root + 'spec/fixtures/v2.ics') 
}

Let us know how it goes!

Hi @pgraham3,
Sorry if I wasn't clear enough; my problem is not with the Content-Disposition but the additional info in the Content-Type. It doesn't seem to be passed through. e.g. the method=REQUEST part.

Got it @rmaspero!

So it looks like this has come up recently on #97 and was solved there. I imagine your use case would be for dynamically generating ICS invites rather than attaching files directly?

Here is a quick example of working ICS attachment showing the expected 'Add to Calendar' prompt in Gmail (requires the icalendar gem)

class CalendarMailer < ApplicationMailer
  def test_ics_mail
    # Create a calendar with an event (standard method)
    cal = Icalendar::Calendar.new

    cal.event do |e| # check icalendar gem docs for more details on the options available
      e.dtstart     = 1.day.from_now + 1.hour
      e.dtend       = 1.day.from_now + 3.hours
      e.summary     = "Some event"
      e.description = "With a description"
    end

    attachments["appointment"] = { mime_type: "text/calendar", content: cal.to_ical }

    mail(to: "<INVITEE@DOMAIN.COM>", subject: "Calendar attachment test")
  end
end

With the icalendar gem you can dynamically generate your invites and won't have to store .ics files.

Tested in Rails console (development environment) via CalendarMailer.test_ics_mail.deliver

Screenshot 2023-09-01 at 3 09 00 PM

I'll also get this use case added to the wiki.

Hi @pgraham3,
Thanks, I had already looked at that issue and its solutions. My implementation already does these bits but many other email clients don't recognise an attached .ics file as an invite like Gmail's web interface.

I already have my .ics file created and am testing using one I know that my email client (MacOS Mail, and Outlook) when attached correctly automatically detect.

My problem is that Outlook and MacOS Mail appear to need the the content type to have the method request bit to show/detect the invites correctly Content-Type: text/calendar; method=REQUEST.

Postmark doesn't seem to be passing these through when set using the ActionMailer attachments API.

Thanks for the additional info @rmaspero!

Setting the Content-Type after initializing the attachment works for using an existing ICS file, like this:

def test_file_ics_mail
    attachments["v2.ics"] = {
      name: "v2.ics",
      content: File.read(Rails.root + "spec/fixtures/v2.ics")
    }

    # MiniMime does not have an existing Content-Type for what we need by default so let's set it directly instead
    # of letting MiniMime infer the Content-Type
    attachments["v2.ics"].content_type = "text/calendar; charset=utf-8; method=REQUEST"

    mail(to: "RECIPIENT@DOMAIN.COM", subject: "Calendar attachment test")
end

Please give that a shot.

Shows expected result in Gmail:

Screenshot 2023-09-05 at 9 01 50 AM

Hi @pgraham3,
So despite setting that explicitly the Content-Type doesn't seem to be carried through and postmark sends:

Content-Type: text/calendar; name=v2.ics
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=v2.ics

Not sure where it is getting overwritten, but assume it is either not being passed to postmark correctly by the gem or postmark itself is rewriting the Content-Type.

@rmaspero Good point...if I send directly through the Postmark API the Content-Type method=request bit is preserved, as expected. It is also present if I inspect the attachment when sending using the above example code:

Screenshot 2023-09-06 at 11 01 24 AM

Yet as you are seeing it is missing when I check the email source in Gmail.

Let me spend some time investigating the base postmark gem, which handles the interactions with the Postmark API, to see if attachment headers are being modified there.

Alright I believe I found our root issue and a workaround, @rmaspero!

For background see this closed issue from 2019 in the base postmark gem.

To get the method=request bit of the header to propagate through, please add an initializer to monkey patch it for now:

# config/initializers/mail_attachments_patch.rb

module Mail
  class Message
    protected

    def export_native_attachments
      attachments.map do |attachment|
        basics = {
          "Name" => attachment.filename,
          "Content" => pack_attachment_data(attachment.body.decoded),
          "ContentType" => attachment.content_type
        }
        specials = attachment.inline? ? { "ContentID" => attachment.url } : {}

        basics.update(specials)
      end
    end
  end
end

Sending via this mailer code below resulted in the correct header appearing in Gmail/Postmark after using the above initializer:

def test_file_ics_mail
    attachments["v2.ics"] = {
      content: File.read("spec/fixtures/files/v2.ics"),
      headers: {
        "Content-Type" => "text/calendar; name=v2.ics; method=request",
      }
    }

    mail(to: "someone@somewhere.com", subject: "Calendar attachment test")
  end
Screenshot 2023-09-06 at 2 01 24 PM

We will work on getting that monkey patch fix implemented into the base postmark gem. Once that is addressed you will be able to remove the patch.

Closing this as it is an issue with the base postmark gem causing the attachment header discrepancy. Follow issue for fix status and use above workaround for now.

@pgraham3 hugely appreciate the work in finding the root cause of the issue. I've tested the monkey patch and that is working for me 🙌🏻

Awesome @rmaspero - thanks for your patience while we figured it out!