How to create a multipart ical email? [Rails] - ruby-on-rails

What I want to achieve is the following:
Send an email with delayed_job containing:
plain-text
html (will be displayed by regular clients which don't understand the inline ical)
"inline" ical which is recognized by Outlook and Thunderbird (with Lightning).
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:
in Apple's Mail 2 attachments show up (instead of one):
(the html is displayed fine)
in Thunderbird (Lightning) I do get an invitation, just like I want. But the Alarm does not show up.
I have to do some REALLY disgusting gsubs on the rendered iCal in order for the ATTENDEES to show up. (see code snippet)
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:
multipart/mixed
multipart/alternative
text/plain
text/html
text/calendar (with: method=REQUEST)
application/ics
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:
Rails 3.2.13
The Icalendar gem I'm using

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.

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 ?

Related

How to delete all events from Google calendar from specified date?

I'm using googlecalendar plugin for working with Google Calendar with ruby.
The only problem I'm facing is with deleting an event from google calendar.
I found an example to delete event in above mentioned github. But we need to pass event id. How can I use the below code to delete all events from google calendar from specified date?
CODE:
require File.dirname(__FILE__) + '/../shared.rb'
g = GData.new
puts 'login'
token = g.login('REPLACE_WITH_YOUR_MAIL#gmail.com', 'REPLACE_WITH_YOUR_PASSWORD')
puts "token: #{token}"
event = {:title=>'title',
:content=>'content',
:author=>'pub.cog',
:email=>'pub.cog#gmail.com',
:where=>'Toulouse,France',
:startTime=>'2009-06-20T15:00:00.000Z',
:endTime=>'2009-06-20T17:00:00.000Z'}
create_response = g.new_event(event)
puts create_response.body
puts 'delete_event'
# TODO GET id from new_event response
id='http://www.google.com/calendar/feeds/default/private/full/pgvgjdnh43g3bo0emnpfg0gnr4'
response = g.delete_event(id)
puts 'done'
URL:
https://github.com/francisoud/googlecalendar/blob/master/googlecalendar/examples/ruby_standalone/gdata_delete_event.rb
Please share your thoughts if we can do this with other plugins.
Thanks in Advance :)
A search on github brought up this google_calendar gem that seems to be the more popular gem (if not the standard). It is also better documented and organized.
This is what I came up with:
require 'google_calendar'
cal = Google::Calendar.new(:username => 'username#gmail.com',
:password => 'password',
:app_name => 'delete_events')
cal.events.each { |event|
event.delete if Time.parse(event.start_time) >= Time.new(2011,10,14)
}
Time.parse puts each event's start time into a format ruby can understand and then compares it to your given date October 14, 2011. If the event is on or after the event, it is deleted. To delete events before do:
if Time.parse(event.start_time) <= Time.new(2011,10,14)
or for all events on a certain date do:
if Time.parse(event.start_time) == Time.new(2011,10,14)

How do I get the text part only from Mailman email?

I'm using the Mailman gem to process incoming email for my Rails app. My application looks for a YAML document in the plain-text email and then loads it into a Ruby object for further manipulation by the app.
However, I want to be able to plan ahead for email clients that might respond with a multi-part email. I need to get the plain-text part of the email and pass it into the YAML parser.
For some reason, it's still having problems parsing the YAML. I'm guessing because it's not really getting the plain text part here.
Is there a better way to get the text/plain part of an email with Mailman? Should I scrap Mailman and just get down and dirty with ActionMailer instead?
Mailman::Application.run do
default do
begin
message.parts.each do |part|
Mailman.logger.info part.content_type
if part.content_type == 'text/plain; charset=ISO-8859-1' # My poor way of getting the text part
the_yaml = part.body.decoded.scan(/(\-\-\-.*\.\.\.)/m).first.last # Find the YAML doc in the email and assign it to the_yaml
ruby_obj = YAML::load(the_yaml.sub(">", "")) # Remove any >'s automatically added by email clients
if ruby_obj['Jackpots']
ruby_obj['Jackpots'].each do |jackpot|
jp = Jackpot.find(jackpot['jackpot']['id'])
jp.prize = jackpot['jackpot']['prize']
jp.save
end
end
end
end
rescue Exception => e
Mailman.logger.error "Exception occurred while receiving message:\n#{message}"
Mailman.logger.error [e, *e.backtrace].join("\n")
end
end
end
I was able to find a little bit better way to handle getting the text part of the email.
Mailman::Application.run do
default do
begin
if message.multipart?
the_message = message.text_part.body.decoded
else
the_message = message.body.decoded
end
the_yaml = the_message.sub(">", "").scan(/(\-\-\-.*\.\.\.)/m).first.last
ruby_obj = YAML::load(the_yaml)
if ruby_obj['Jackpots']
ruby_obj['Jackpots'].each do |jackpot|
jp = Jackpot.find(jackpot['jackpot']['id'])
jp.prize = jackpot['jackpot']['prize']
jp.save
end
end
rescue Exception => e
Mailman.logger.error "Exception occurred while receiving message:\n#{message}"
Mailman.logger.error [e, *e.backtrace].join("\n")
end
end
end
And then after running it through the debugger and inspecting after the text part was successfully parsed. It would get hung up on the YAML loading. Turns out, a couple of my lines were too long, to the email client inserted a newline, breaking a comment in my YAML, and thus breaking the whole YAML document.

Rails 3.0.7 ActionMailer attachment issue

I'm trying to attach a file to an outgoing email but the attachment size ends up being 1 byte. It doesn't matter what attachment I'm forwarding it always ends up in the email 1 byte in size (corrupt). Everything else looks ok to me.
The email information is pulled from an IMAP account and stored in the database for browsing purposes. Attachments are stored on the file system and it's file name stored as an associated record for the Email.
In the view there's an option to forward the email to another recipient. It worked in Rails 2.3.8 but for Rails 3 I've had to change the attachment part of the method so now it looks like...
def forward_email(email_id, from_address, to_address)
#email = Email.find(email_id)
#recipients = to_address
#from = from_address
#subject = #email.subject
#sent_on = Time.now
#body = #email.body + "\n\n"
#email.attachments.each do |file|
if File.exist?(file.full_path)
attachment :filename => file.file_name, :body => File.read(file.full_path)
else
#body += "ATTACHMENT NOT FOUND: #{file.file_name}\n\n"
end
end
end
I've also tried it with...
attachments[file.file_name] = File.read(file.full_path)
and adding :mime_type and :content_type to no avail.
Any help would be a appreciated.
Thanks!
This is what I tried and worked for me
attachments.each do |file|
attachment :content_type => MIME::Types.type_for(file.path).first.content_type, :body => File.read(file.path)
end
Is the file readable? Can you debug the issue by placing something like this?
logger.debug "File: #{file.full_path.inspect} : #{File.read(file.full_path).inspect[0..100]}"
Is there anything in your development.log?
Well, someone from the rails team answered my question. The problem lies with adding body content (#body) other than the attachment inside the method. If you're going to attach files you have to use a view template.

ruby/rails receiving plain text email with pop

I have used the pop mailer for ruby (net/pop)
The problem I am having is that some of the emails are in HTML format is there a way to specify that I want plain text ?
Thanks, Alex
Emails can come in different formats. The most common is MIME which allows an email to contain multiple "parts". Commonly an HTML and a plain-text part. However, you can not control which parts the email actually contains. This can only the sender for obvious reasons.
You can however use ruby to get the plain text part if one is present or try to generate some representation of that from the HTML part.
The following condensed example to get the plain text part of an email is from the MailHandler model and the POP3 module of Redmine (licensed under GPLv2).
def plain_text_body(email)
parts = email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
if parts.empty?
parts << email
end
plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
if plain_text_part.nil?
# no text/plain part found, assuming html-only email
# strip html tags and remove doctype directive
plain_text_body = strip_tags(email.body.to_s)
plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
else
plain_text_body = plain_text_part.body.to_s
end
plain_text_body.strip
end
pop = Net::POP3.APOP(true).new(host,port)
pop.start(username, password) do |pop_session|
if pop_session.mails.empty?
puts "No email to process"
else
puts "#{pop_session.mails.size} email(s) to process..."
pop_session.each_mail do |msg|
message = msg.pop
plain_text = plain_text_body(message)
#
# Now do something with the plain text body
#
end
end
end

What's the most efficient way to keep a user database in sync with an external mailing list service?

I'd like some advice on how I should synchronize a list of email addresses on 11k users against an external mailing list program, in this case Mailchimp.
Normally the way I'd do this is simply to have an :after_save callback, to send a single update to the external api.
But already each hour, a rake task is run to update a property on every user in the database. If I simply did that, every hour, the the poor mailchimp API would get be hit 11,000 times.
What's the most efficient, simple way to do this, to check only if a single attribute you're watching has changed from what it was before the save?
If there's a variable that persists across the transaction lifecycle I would simply do something like this, where I check if the value has changed, and if it's different execute come other code.
class User
:before_save :store_old_email
:after_save :sync_with_chimp
def store_old_email
$ugly_of_global_variable_to_store_email = user.email
end
:sync_with_chimp
if $ugly_of_global_variable_to_store_email != user.email
//update_mail_chimp_api
end
end
end
I've checked the rails api here, and I'm still slightly unclear on how I should be doing this.
Would you use the dirty? class here to do this?
This is the way I went with in the end.
It turns out Rails gives you loads of handy callbacks in the dirty to do this.
Any suggestions on how to make this code less repetitive wold be gratefully received.
def update_mailchimp(optin)
# Create a Hominid object (A wrapper to the mailchimp api), and pass in a hash from the yaml file
# telling which mailing list id to update with subscribe/unsubscribe notifications)
#hominid = Hominid.new
client_site_list_id = YAML.load(File.read(RAILS_ROOT + "/config/mailchimp.yml"))
case optin
when 'subscribe_newsletter'
logger.debug("subscribing to newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_newsletter'
logger.debug("unsubscribing from newsletter...")
"success!" if #hominid.subscribe(client_site_list_id['client_site_to_mailchimp_API_link'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'subscribe_monthly_update'
logger.debug("subscribing to monthly update...")
"success!" if #hominid.subscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
when 'unsubscribe_monthly_update'
logger.debug("unsubscribing from monthly update...")
"success!" if #hominid.unsubscribe(client_site_list_id['monthly_update'], email, {:FNAME => first_name, :LNAME => last_name}, 'html')
end
end
# Keep the users in sync with mailchimp's own records - by only firing requests to the API if details on a user have changed after saving.
def check_against_mailchimp
logger.info("Checking if changes need to be sent to mailchimp...")
if newsletter_changed?
logger.info("Newsletter changed...")
newsletter ? update_mailchimp('subscribe_newsletter') : update_mailchimp('unsubscribe_newsletter')
end
if monthly_update_changed?
logger.info("update preferences changed...")
monthly_update ? update_mailchimp('subscribe_monthly_update') : update_mailchimp('unsubscribe_monthly_update')
end
end
you could change your users model to an active resource instead of active record and just use mailchimps api as your db for users
this is an older post about active resource but might get you started down the right path
http://www.therailsway.com/2007/9/3/using-activeresource-to-consume-web-services

Resources