Opening Eudora for Mac OS X mailboxes as a result of Procmail filtering

I've used Eudora for the Mac for nearly a decade. After all this time, it's still the best mailer I've found. For the last few years, it has had a fairly robust filtering feature that made it easy to deal with the volume of mail I receive. But I've passed a certain point. I now have so many filter rules that it's hard to keep them organized. And I've started to read my mail from multiple locations using IMAP. It's a pain to keep my Eudora Filters synchronized on my Mac at home and my Mac at work. I also use other IMAP mail readers when I need to check my mail and I'm not at home or at work.

Enter procmail . Procmail runs on my mail server and makes it "easy" for me to write filter rules to categorize my mail and to organize these rules in various ways. For example, I put all of the rules relating to mailinglists in one file, and all of the rules relating to individual correspondents in another file, each sorted alphabetically.

The drawback? Eudora has a feature which allows it to open a mailbox window whenever it filters mail into that mailbox. Without that feature, it's hard to notice that you have even received new mail, and annoying to have to open the mailbox and read the mail. Eudora could use IMAP subscriptions or periodic checks to do the same thing when a different program puts mail into a mailbox, but the folks at Qualcomm haven't seen fit to include that feature yet. Maybe if more people make a feature request for it, they'll include it in a future version...

Loath to give up this feature, I recently realized that I could get a close approximation of it with some programming glue. Procmail creates a log that notes when it puts mail into a mailbox. I have a server process sitting on my mailserver and watching that log. When a client connects, the server informs it of all of the new messages that have arrived since the last time a client checked in and where they've been filtered to. The client then calls an AppleScript that sends a message to Eudora, telling it to open the appropriate mailboxes.

The server

#!/usr/bin/perl

# change these two lines
$server_port = 1234;
$identification_phrase = 'insert phrase here';

# redirect error messages to a logfile
open(STDERR, ">>/tmp/logserver.log");

# code stolen liberally from Perl Cookbook

use IO::Socket;

$logfile = $ARGV[0];
open(LOG, "<$logfile");
# read to the current end of the log
while(<LOG>) {
}

$server = IO::Socket::INET->new(LocalPort => $server_port,
                                Type      => SOCK_STREAM,
                                Reuse     => 1,
                                Listen    => 10 )   # or SOMAXCONN
    or exit; # "Couldn't be a tcp server on port $server_port : $@\n";

while (($client,$client_address) = $server->accept()) {
    ($client_port, $client_ip) = unpack_sockaddr_in($client_address);
    @decimal_address = unpack("C4", $client_ip);
    $ip_number = join(".", @decimal_address);
    warn "connection from $ip_number";
    while(<$client>) {
	last if /$identification_phrase/;
    }
    warn "connection from $ip_number gave pass phrase";
    $lines = 0;
    while(<LOG>) {
	$lines++;
	print $client $_;
    }
    warn "returned $lines lines to $ip_number";
    close($client);
}

close($server);

The client

#!/usr/bin/perl

# make these match the server
$remote_host = 'my.mail.server'
$remote_port = 1234;
$identification_phrase = 'insert phrase here';
$open_mailbox_binary = '/Users/aneel/bin/open_mailbox.pl';

# redirect error messages to a logfile
open(STDERR,">>/tmp/logclient.log");

# make sure Eudora is running
unless ($running = `ps auxwww | grep Eudora | grep -v grep`) {
    warn "Eudora NOT running at " . localtime() . "\n";
    exit;
}

# code stolen liberally from Perl Cookbook

use IO::Socket;

$socket = IO::Socket::INET->new(PeerAddr => $remote_host,
                                PeerPort => $remote_port,
                                Proto    => "tcp",
                                Type     => SOCK_STREAM)
    or die "Couldn't connect to $remote_host:$remote_port : $@\n";

print $socket "$identification_phrase\n";

while (<$socket>) {
    #warn $_;
    chomp;
    if (m/^\s*From\s*(.*)/) {
	$from = substr($1, 0, 60);
    } elsif (m/^\s*Subject:\s*(.*)/) {
	$subject = substr($1, 0, 60);
    } else {
	$folder = "";
	if (m{^\s*Folder: /var/mail/}) {
	    $folder = 'Inbox';
	} elsif (m{^\s*Folder:\s+(.*/)?([^/]*[^\s/])\s+\d+}) {
	    $folder = $2;
	}
	if ($folder) {
	    # From turns out to be useless, in this day of mailman
	    # lists. Everything gets listed as coming from list-admin
	    # or some such nonsense
	    #$folders{$folder} .= " From: $from\n      Subject: $subject\n";
	    $folders{$folder} .= "    $subject\n";
	}
    }
}

if (%folders) {
    warn "\n" . localtime() . "\n";
    foreach (sort keys %folders) {
	warn "$_\n$folders{$_}";
    }
    foreach (sort keys %folders) {
	if ($_ ne 'spam') {
	    system($open_mailbox_binary, $_);
	}
    }
}

The program to open the mailbox

It would be cool if this were just an AppleScript, but it turns out that the way to call AppleScripts from the command line is somewhat broken. From the osascript manpage:

BUGS
     osascript does not yet provide any way to pass arguments to the script.
So I've constructed a perl script that makes a custom AppleScript and calls it:
#!/usr/bin/perl

open(STDERR, ">>/tmp/logclient.log");

foreach $mailbox (@ARGV) {
    $script = <<SCRIPT;
on findMailboxNamed(parentFolder, target)
    tell application "Eudora"
	repeat with x from 1 to count every mailbox of parentFolder
	    if (name of mailbox x of parentFolder) is equal to target then
		return (a reference to mailbox x of parentFolder)
	    end if
	end repeat
	repeat with x from 1 to count every mail folder of parentFolder
	    set subFind to my findMailboxNamed((a reference to mail folder x of parentFolder), target)
	    if subFind is not false then
		return subFind
	    end if
	end repeat
    end tell
    return false
end findMailboxNamed


tell application "Eudora"
    set M to my findMailboxNamed((a reference to mail folder "ÇDominantÈ"), "$mailbox")
    close M
    open M
end tell
SCRIPT

    @script = split("\n", $script);
    @script = map { "-e '$_'"} @script;
    
    system('/usr/bin/osascript ' . join(' ', @script));
} 
I'm sure my AppleScript idiom sucks. This is the only AppleScript I've ever written, and the process of writing it was fairly painful.

Setup

The server runs on the mailserver that procmail runs on. I have it in my crontab so that, if it dies it gets restarted. Here's the cron entry:

0 * * * * /home/nazareth/mac/logserver.pl /home/nazareth/procmail/log

The client runs on all of the Macs that I run Eudora on. I have it in my crontab to run every 5 minutes. Here's the cron entry:

*/5 * * * * /Users/aneel/bin/logclient.pl

The mailbox opener gets called by the client.


Last Modified 19 Sep 2002 by Aneel Nazareth