Programming

Software | Secret Software | Writing

Reading mail from Thunderbird

You'd think that reading mail in Thunderbird ought to be really easy - after all, Thunderbird's a mail client, that's what it's for. But I'm talking about reading mail from inside of Javascript - getting access to the body of a given message. This turns out to be a little more tricky.

First, there is no object which corresponds directly to a mail message. The best you've got is the nsIMsgDBHdr interface, which corresponds to an email's header plus a few more useful bits of metadata. From this you can find a mail's "key", which is the internal value that Thunderbird uses to identify it, its folder, and from thence you can read its body.

How you do this depends first on whether the mail is offline or online. To discover this, we ask the folder:

   if (mail.folder.hasMsgOffline(mail.messageKey)) {
       readOffline(mail);
   } else {
       readOnline(mail);
   } 

Reading mail offline

If a mail is offline, we can directly ask its folder to give us an input stream representing the body of the email, using the getOfflineFileStream method. This takes the message key, plus two more parameters that we don't care about so we pass in dummy objects to:

    function readOffline(mail) {
        var key = mail.messageKey;
        var offset = new Object();
        var messageSize = new Object();
        var is;
        try{ is = mail.folder.getOfflineFileStream(key,offset,messageSize); }
        catch(e){ alert("message: "+e.message); }

Now unfortunately to read from this stream, we need another stream, a nsIScriptableInputStream, which we initialize with the output of getOfflineFileStream, and then we read from:

        try{
            var sis = factory.createInstance(
                Components.interfaces.nsIScriptableInputStream
            );
            sis.init(is);
            var bodyAndHdr;
            while(sis.available()) { bodyAndHdr += sis.read(2048) }
        } catch(e){ alert("message: "+e.message); }
        doSomethingWith(bodyAndHdr);
    }

Hey presto, the message body - and its headers - ends up in bodyAndHdr. You do the separation and parsing yourself, at least until I finish writing this Javascript mail library I've got going on...

Online reading

Online reading is more complicated because you have to ask the IMAP server to tell you about the message; this happens asynchronously - that is, it takes your order and gets back to you. So you have to listen for it getting back to you. Let's first place our order.

We do this by first getting the message's URI, which we get from the folder, and then finding the message service - IMAP, POP, etc. - which handles this mail.

    function readOnline(folder, mail) {
        var key = mail.messageKey;
        var uri = mail.folder.generateMessageURI(key);
        var messenger = Components.classes['@mozilla.org/messenger;1']
                        .createInstance();
        messenger = messenger.QueryInterface(
                        Components.interfaces.nsIMessenger
                    );
        var messageService = messenger.messageServiceFromURI(uri);

Aside: You'll see this QueryInterface pattern a lot in Mozilla programming; if you're not familiar with it, think of it as a cast to a particular class. createInstance always returns an xpconnect wrapped nsISupports object, which is basically a proxy object. We want to use it via its nsIMessenger interface, so we "cast" it to that interface with QueryInterface.

OK, now we have a message service which handles our mail's URI. We set up another dummy object, and call CopyMessage, which gets the message from the server and puts it in a stream for us, allowing us to pass in a nsIStreamListener to respond to things happening (like data arriving) on that stream:

        var aurl = new Object();
        messageService.CopyMessage(uri,
            myStreamListener, false, null, msgWindow, aurl);
    }

And that's all for this function. The stream listener will handle the data when our server gets back to us with it. The stream listener looks like this:

    var myStreamListener = {
         onDataAvailable: 
            function(request, context, inputStream, offset, count){
                try {
                    var sis= Components.
                             classes["@mozilla.org/scriptableinputstream;1"]
                             .createInstance(
                                Components.interfaces.nsIScriptableInputStream
                             );
                    sis.init(inputStream);
                    bodyAndHdr += sis.read(count);
                }
                catch(ex) { alert("exception caught: "+ex.message+"\n"); }
         },
         onStartRequest: function(request, context) { },
         onStopRequest: function(aRequest, aContext, aStatusCode) {
             doSomethingWith(bodyAndHdr);
         }
    };

The concept you should recognise from the offline reader: we're using a scriptable input stream to talk to the stream that Mozilla gives us, and read from it into our variable. The onDataAvailable function will get called repeatedly until the mail server tells us we've got the whole of the message; at this point, onStopRequest is called, and we can process the mail.

Acknowledgements.

Without the Sync Kolab extension, I would have been groping in the dark for weeks. As it was, I was only groping in the dark a few days.

Latest articles

Development activity

This page was last checked for correctness on 2007-03-21. Contact Simon.