fringe geekery

a collection of problems and solutions for random combinations of technology

Sep 25, 2009

facebook push to iphone

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:

  1. modify postfix's main.cf mailbox_command = /usr/local/libexec/dovecot/deliver

  2. 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}

  3. install the sieve plugin FreeBSD ports FTW $ cd /usr/ports/mail/dovecot-sieve/
    $ make install clean

  4. modify dovecot.conf protocol lda {
      ...
      mail_plugins = sieve
      ...
    }
more info here

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"];
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";
  }
}
in prose, form
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
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
Did you know iPhone apps can register URL scheme handlers? here's a list. Notably, facebook supports fb://profile, fb://requests, etc...
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...

Sep 24, 2009

traffic conditions for your current location (CA iPhone)

http://sigalert.com has a slick iphone interface. You can choose to view traffic conditions for your current location and it will prompt you to allow sending your GPS info to the site. The subsequent URL has your latitude and longitude as parameters. I wanted a shortcut on my home screen to always load the site given my current location. This is a task in response agility.

  1. open a new tab in safari
  2. copy this URL into the location bar
  3. hit Go
  4. before the web server redirects you to a new URL with lat + long, cancel the operation by hitting X in the location bar
  5. hit the + to add a bookmark, choose to add to home screen
If the site icon is blank, try again, unless you're ok with a blank icon on your home screen. The timing window is after the site icon loads but before the server redirects you.

setting up DKIM

DKIM (DomainKeys Identified Mail) is a method for email authentication that allows an organization to take responsibility for a message in a way that can be validated by a recipient. Read more about it at the wikipedia entry

ingredients used: FreeBSD 7.x, Postfix 2.6.2, dkim-milter 2.8.3, Bind 9.6.1
prerequisites: a working Postfix and Bind installation
instructions for windows here

  1. install dkim $ cd /usr/ports/mail/dkim-milter
    $ make install clean
    $ echo "milterdkim_enable='YES'" >> /etc/rc.conf
    $ echo "milterdkim_uid='postfix'" >> /etc/rc.conf # use the same uid as the postfix service

  2. setup keys $ mkdir -p /var/db/dkim/domains/example.com
    $ cd /var/db/dkim
    $ dkim-genkey # this creates a key and domain record file
    $ mv default.private domains/example.com/my_awesome_selector
    $ cat default.txt >> /etc/named/your_example.com_zone_file
    $ echo "*@example.com:example.com:/var/db/dkim/domains/example.com/my_awesome_selector" >> keylist
    repeat these steps for additional domains
    see the man page dkim-filter.conf(5) for more info on the keylist content format

  3. edit /usr/local/etc/mail/dkim-filter.conf
    I used mostly default settings with the following exceptions DNSTimeout 5 # this should be lower than postfix's timeout
    Domain example.com # this may not be necessary given the use of a keylist file
    KeyList /var/db/dkim/keylist
    On-Default accept # shit happens, don't freak out on DNS lookups, etc...
    Socket local:/var/run/milterdkim/sock
    Syslog yes # the default syslog facility is 'mail'

  4. configure postfix
    add the following lines to main.cf milter_default_action = accept
    milter_protocol = 3
    smtpd_milters = unix:/var/milter-greylist/milter-greylist.sock unix:/var/run/milterdkim/sock
    non_smtpd_milters = unix:/var/run/milterdkim/sock

  5. re/start services /etc/rc.d/named reload
    /usr/local/etc/rc.d/milter-dkim start
    /usr/local/etc/rc.d/postfix reload
make sure it works! you can send a test mail to check-auth@verifier.port25.com and you will receive a report including DKIM info.