wpp
wpp

Reputation: 7333

How to create a multipart ical email? [Rails]

What I want to achieve is the following:

Send an email with delayed_job containing:

  1. plain-text
  2. html (will be displayed by regular clients which don't understand the inline ical)
  3. "inline" ical which is recognized by Outlook and Thunderbird (with Lightning). Thunderbird Accept/Decline/Tentative Buttons
  4. a "regular" ical attachment (for #2)

What works so far/what does'nt:

I am able to send the email via delayed_job with all parts, however:

Apple Mail

(the html is displayed fine)

Lightning

My thinking:

The first thing to keep in mind is: in order to send an email with attachments from delayed_job

To fix this, remember to add this line to your mailer: content_type "multipart/mixed"

As far as I understand the correct MIME-Type hierarchy would therefore be:

Warning! code incoming.

I currently construct this email in the following manner:

Edit: I updated the mailer for Rails 4.2 (attachments must be placed before mail)

in my mailer.rb

def invitation_email(...)  
  subject = "I suck at email..."
  attachments["invite.ics"] = { mime_type: "application/ics",
                                content: ical_attachment }
  email = mail(from: me, to: you, subject: subject)
  add_ical_part_to(email)
  email
end

def add_ical_part_to(mail)
  outlook_body = ical_attachment
  mail.add_part(Mail::Part.new do
    content_type "text/calendar; method=REQUEST"
    body outlook_body
  end)
end

and this is how I construct the ical attachments:

def ical_attachment
  params_participant = {
        "ROLE"   => "REQ-PARTICIPANT",
        "RSVP" => "FALSE",
        "PARTSTAT" => "ACCEPTED"
  }

  params_invite = {
        "CUTYPE" => 'INDIVIDUAL',
        "ROLE"   => "REQ-PARTICIPANT",
        "PARTSTAT" => "NEEDS-ACTION",
        "RSVP" => "TRUE"
  }

  cal = Icalendar::Calendar.new

  event = Icalendar::Event.new
  event.dtstart      @party.from.to_datetime, { "VALUE" => "DATE" }
  event.dtend        @party.to.to_datetime, { "VALUE" => "DATE" }
  event.summary      @party.title
  event.description  @party.description
  event.klass        "PRIVATE"
  event.organizer    "cn=#{@user.name} #{@user.surname}:mailto:#{@user.email}"

    # THIS DOES NOT WORK
  event.alarm.trigger =  "-PT5M" # 5 Minutes before...

  @party.participations.each do |participation|
        str = "cn=#{participation.user.name} #{participation.user.surname}:mailto:#{participation.user.email}"
        event.add_attendee(str, params_participant)
  end

  @party.invitations.each do |invitee|
        event.add_attendee("mailto:#{invitee.email}", params_invite)
  end
  cal.add_event(event)
  cal.publish
  
  # I KNOW THIS IS HORRIBLE AND I HATE IT, BUT OTHERWISE THE ATTENDEES DO NOT SHOW UP
  cal.to_ical.gsub("ORGANIZER:", "ORGANIZER;").gsub("ACCEPTED:", "ACCEPTED;").gsub("TRUE:", "TRUE;").gsub("PUBLISH", "REQUEST")
end

Any help would be really appreciated!

The email that is being generated: http://pastebin.com/patf05zd

Oh and I'm on:

Upvotes: 4

Views: 1842

Answers (2)

wpp
wpp

Reputation: 7333

In case someone else happens to come across this, here is what I did:

Instead of the icalendar gem I now use ri_cal. Although I was skeptical because the last commit to that repo was 3 years ago, the google group was a very helpful resource.

Here is how I generate the ical attachment (both inline and normal), which seems to be working fine (although it obviously needs some refactoring :))

def to_ical
# this is horrible
klass = self

cal = RiCal.Calendar do
  event = event do
    organizer   "CN=#{klass.user.name} #{klass.user.surname}:mailto:#{klass.user.email}"
    summary     klass.party.title

    description klass.ical_description
    dtstart     klass.party.from.utc.to_datetime
    dtend       klass.party.to.utc.to_datetime
    location    "See url in description"
    security_class klass.security_class

    # this is horrible
    h = self

    klass.party.participations.each do |participation|
      h.add_attendee klass.prepare_participant(participation)
    end

    klass.party.invitations.each do |invitee|
      h.add_attendee klass.prepare_invitee(invitee.email)
    end

    unless klass.party.reminder == 0
      alarm do
        description "Alarm description"
        trigger klass.convert_trigger # -PT1H
        action "DISPLAY"
      end
    end
  end
end

# THE HORROR
cal.to_s.gsub("ATTENDEE:", "ATTENDEE")
        .gsub("ORGANIZER:", "ORGANIZER;")
        .gsub("CALSCALE:GREGORIAN", "CALSCALE:GREGORIAN\nMETHOD:REQUEST\n")

end

The 2 Attachments in Apples Mail still show up, I don't think that can be fixed.

Upvotes: 2

Arnaud Quillaud
Arnaud Quillaud

Reputation: 4645

Your second B64 encoded attachment contains a lot of garbage towards the end (attendee field). That would explain the Thunderbird issue.

Please note that some clients will ignore any alarm you may set on a REQUEST: As an organizer, you should not dictate when each attendee should be reminded of the meeting. That would be a rather rude thing to do.

Regarding the Apple iCal issue, there is not much you can do I'm afraid: Some clients want the ics within, some as an attachment so you have to provide both. Does it show the accept/decline panel on iCal ?

Upvotes: 1

Related Questions