Mail to WAP Gateways

It's coming up to Valentine's day again, and invariably my thoughts turn back to last year's rather memorable weekend-break to Stockholm, in which I learned two things:

  1. Stockholm makes a great Valentine's destination.
  2. My girlfriend of the time was not happy with me cracking out my iBook and checking my email halfway into the break.

The relationship, predictably, didn't last much longer, but it did occur to me that a quick and easy way to check my email when away from my computer would be very useful. One of the items that travels everywhere with me, and has some limited Internet access is my phone -- although admittedly this has only WAP access. WAP access, it seemed, would have to do...

The tool I ended up building fills my needs very well, but possibly won't be such a great match for others. This article looks at considerations when rendering email for display online, especially when space is very limited.

Overview of Messages

The first challenge is reading the contents of our target mailbox. For this, we turn to the Perl Email Project's Email::Folder:

 use Email::Folder;
 
 my $folder = Email::Folder->new( '/home/sheriff/mbox' );
 
 for my $message ( $folder->messages ) {
 
        ...

Email::Folder's messages() function returns Email::Simple objects. For my folder-view, I chose to group messages by date, and use the sender's "real name" as the subject. Something like:

 30 Jan 2004
    Michael Roberts
  * Paul Makepeace
    Uri Guttman
 29 Jan 2004
    Kate Pugh

Extracting header fields from Email::Simple objects couldn't be simpler:

 my $from = $message->header('from')

But people familiar with the various email RFCs will know that since email headers have to use only printable US-ASCII, they're very often encoded: your header field might well look like:

  =?iso-8859-1?q?Pete=20Sergeant?= <pete@clueball.com>

This will not look pretty if you use it literally. Thankfully, MIME::WordDecoder exports the function unmime -- rendering the above as "Pete Sergeant <pete@clueball.com>."

Getting the date from an email is also somewhat nontrivial -- an example "Date" header looks like:

 Fri, 30 Jan 2004 14:09:51 -0000

And that's if you're lucky, and it's well-formed, without starting to think about time zones. If we want to do anything useful with dates, we're going to want the date as an epoch time. Luckily, DateTime::Format::Mail steps in, and not only parses our date, but returns a highly useful DateTime object, allowing us to do all kinds of fun date stuff. To simply reformat the date as Day/Month/Year:

 my $datetime = DateTime::Format::Mail->new( loose => 1 );
 my $time = $datetime->parse_datetime( $message->header('date') );
 my $day_month_year = $time->dmy;

Finally, we're going to want to know if an email is new or not. Luckily, most MUAs will set/edit an email's status header. Rather than checking if an email is new, we check if it's been read -- denoted by a R in the status header:

 $new_flag++ if $message->header('Status') !~ m/R/;

Now let's put this all together to produce a listing of a folder. We'll use the well-known Schwartzian transform to make the sorting efficient, but unlike the usual practice, we keep the array reference around, as we'll be using the date as well.

 use Email::Folder;
 use MIME::WordDecoder qw( unmime );
 use DateTime::Format::Mail;

 my $folder = Email::Folder->new( '/home/sheriff/mbox' );
 my @to_sort;
 my $prev_date = "";
 for (sort { $a->[1] cmp $b->[1]    }
      map  { [$_, message2dmy($_) ] } 
      $folder->messages) {
     my ($message, $date) = @$_;
     if ($date ne $prev_date) { print $date, "\n"; $prev_date = $date; }
     print $message->header('Status') =~ m/R/ ? "   " : " * ";
     print unmime($message->header('from')), "\n";
 }

 sub message2dmy {
     my $message = shift;
     my $datetime = DateTime::Format::Mail->new( loose => 1 );
     my $time = $datetime->parse_datetime( $message->header('date') );
     my $day_month_year = $time->dmy;
 }

Displaying Individual Messages

Those are the main challenges of a folder-view. Viewing an individual message presents a different set of challenges.

First and foremost is the appalling habit people have of sending each other HTML-"enriched" emails, with all sorts of attachments. If you're trying to read the email on a cell phone over a slow connection, you don't want to be battling with this -- you want a nice plain-text representation of the email. So, Email::StripMIME is your friend. Assuming we have an Email::Simple object, we can simply:

 my $string = $email_simple_object->as_string();
 $string = Email::StripMIME::strip_mime( $string );
 $email_simple_object = Email::Simple->new( $string );

Of course, if we really wanted to cut down on the amount of content we're receiving, and we're only using this tool to get an overview of our messages, we can cut out quoted text, remnants of the email that the sender was replying to, and so on. Text::Original does just this for us, as well as stripping out attribution lines:

 my $body = $email_simple_object->body();
 $body = first_lines( $body, 20);
 $email_simple_object->body( $body );

The final problem is in creating actual real WML. Sadly, this is nontrivial, and in the past, I've tended to resort to outputting it by hand. But it doesn't have to be that way --; CGI::WML just about handles the task for us. CGI::WML is a subclass of CGI, with methods specific to WAP.

Conclusion

There is no fully working demo at the end of this article. My personal tool works in a way that's probably a little too specific for most people's needs. Hopefully however, it's introduced you to one or more modules you didn't know existed, and given you some inspiration to tinker around with Perl and email-handling.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Monthly Archives

Powered by Movable Type 5.13-en