the problem:
Facebook sends a lot of emails. I generally don't mind the default setting which sends an email everytime your friend sneezes, but I wanted certain kinds of notifications to "bubble up to the top". I recently stumbled on a new iPhone app called Notifications which offers a REST API to send push alerts to your iPhone. So, I started working on a solution...
ingredients used: Postfix 2.6.2, Dovecot 1.2.4, Dovecot Sieve 0.1.12, Ruby 1.8/1.9
prerequisites: a working Postfix and Dovecot installation
instructions for windows here
The basic flow is: mail comes in from facebook, a sieve script determines if the mail is high priority, then the mail is forwarded to a sub-address, a specific .forward file intercepts it and executes a script which utilizes the Notifications REST API
First, go get the Notifications app and register at their site. After creating your account, go to their REST API examples page. You'll see your API key embedded in the examples. You'll need it later...
I use dovecot as an IMAP server and as a MDA. This allows me to utilize the awesomeness that is the sieve plugin. In order for this recipe to work, you don't necessarily need Postfix or Dovecot. You will need a decent sieve interpreter. If you're currently using Postfix and Dovecot but are not using deliver as your MDA, here's a quick and dirty setup:
- modify postfix's main.cf
mailbox_command = /usr/local/libexec/dovecot/deliver
- modify postfix's master.cf
dovecot unix - n n - - pipe
flags=DRhu user=vmail:vmail argv=/usr/local/libexec/dovecot/deliver -c /usr/local/etc/dovecot/dovecot.conf -f ${sender} -d ${user}@${nexthop} -a ${recipient} - install the sieve plugin FreeBSD ports FTW
$ cd /usr/ports/mail/dovecot-sieve/
$ make install clean - modify dovecot.conf
protocol lda {
...
mail_plugins = sieve
...
}
Now we can configure a sieve script. The default location for user sieve scripts is ~/.dovecot.sieve. Here's what mine looks like
require ["envelope", "subaddress", "fileinto", "copy"];
in prose, form
if allof(address :domain :is "from" "facebookmail.com",
not envelope :detail "to" "notify-facebook") {
fileinto "auto_filed.facebook";
if allof(exists "X-Facebook-Notify",
header :contains "X-Facebook-Notify" ["share_comment;", "feed_comment;", "friend;", "msg;", "photo_tag;", "photo_comment;", "photo_album_comment;"]) {
redirect :copy "your_username+notify-facebook@example.com";
}
}
If the mail is from facebook and does not have the sub-address "notify-facebook", file the mail into the sub-folder "auto_filed/facebook". If the mail contains specific facebook headers which we consider high priority, forward a copy to my email address with the sub-address of "notify-facebook".
In this example, the list of "high priority" notifications are when someone comments on one of your links, status, photos, albums or you receive a friend request, private message, or tags you in a photo.
I never found a list of all the message types embedded in the "X-Facebook-Notify" MIME header, if you stumble on such a list, leave a comment.
Postfix (and probably other MTAs) support different .forward files for sub-address. Let's make sure yours is configured properly by adding this to postfix's main.cf
forward_path = $home/.forward${recipient_delimiter}${extension}, $home/.forward
recipient_delimiter = +
Now for our sub-address specific forward file: ~/.forward+notify-facebook. Point to a script which should take one argument of API Key and read mail from STDIN.
"| /usr/local/share/mobile_notification_scripts/facebook.rb <YOUR-NOTIFICATIONS-API-KEY-GOES-HERE>"
Here's the ruby script I use...
#!/usr/local/bin/ruby
Did you know iPhone apps can register URL scheme handlers? here's a list. Notably, facebook supports fb://profile, fb://requests, etc...
require 'rubygems'
require 'tmail'
require 'mechanize'
begin
SINGLE_TOKEN = ARGV[0]
VERSIONLIB = '0.1'
POST_URL = "https://www.appnotifications.com/account/notifications.xml"
exit if SINGLE_TOKEN.blank?
# parse mail
email = TMail::Mail.parse(STDIN.read)
body = (email.multipart?) ? parts.detect { |part| part.content_type == "text/plain" } : email.body
exit if body.blank?
link = (match = body.match(/(http:\/\/www.facebook.com\/n\/.+?)\n/)) ? match[1] : nil
message_details = email.body.gsub(/\n/, "<br/>")
unless link.nil?
message_details = message_details.sub(/#{Regexp.escape(link)}.*/m,'') + <<-HTML
<a href='#{link}'>http://www.facebook.com/...</a><br/><br>
facebook app links:<br/>
<table style="width:100%;">
<tr>
<td style="padding-top:16px;text-align:left;"><a href="fb://feed">News Feed</a></td>
<td style="padding-top:16px;text-align:center;"><a href="fb://requests">Requests</a></td>
<td style="padding-top:16px;text-align:right;"><a href="fb://profile">Profile</a></td>
</tr>
<tr>
<td style="padding-top:30px;text-align:left;"><a href="fb://albums">Albums</a></td>
<td style="padding-top:30px;text-align:center;"><a href="fb://notes">Notes</a></td>
<td></td>
</tr>
</table>
HTML
end
a = WWW::Mechanize.new { |agent|
agent.user_agent = "AppNotifications Ruby #{VERSIONLIB}"
}
# Send a notification
a.post(POST_URL,
{ :user_credentials => SINGLE_TOKEN,
'notification[title]' => 'facebook',
'notification[message_level]' => -1,
'notification[silent]' => 0,
'notification[action_loc_key]' => 'View',
'notification[run_command]' => 'notifications://',
'notification[message]' => email.subject,
'notification[long_message_preview]' => email.subject,
'notification[long_message]' => message_details
}
)
rescue
end
This script configures the push notification badge to automatically open the Notifications app when unlocking your phone. Once in the app, you can click on the latest notification and view the full details which includes a shortened version of the original mail. I also added some facebook app links to the bottom of the notification details.
It seems like a lot of work to get a bullshit feature. True. But it was a fun detour...