Leo / Email / Todo list integration

As an example of the kind of thing you can do with Leo, here are some notes about how I integrated my email, to do list, and Leo. The basic idea is that when I get an email about something that needs to go into my to do list, which is maintained within Leo, I can send it there quickly and easily. And Leo can tell the email software to open the message again.

Part of this is specific to the MH mail system and claws-mail, the mail reader I use, but many other mail readers have similar capabilities. Also a small shell script is used, although that could be replaced with python on a non-shell-script-capable OS. But really the point of this page is to demonstrate Leo integration with other software, I don't expect you to use this system yourself.

Copying email to the todo folder

The first step is to have claws-mail copy the current email to the todo folder. This is accomplished with a claws-mail Action, which is bound to a tool bar button for convenience. The action is simply:

/home/tbrown/bin/claws-todo %f

where /home/tbrown/bin/claws-todo is the following bash script:

NUMBER=$(basename $FILE)
FOLDER=$(echo $FILE | sed 's%/[^/]*$%%; s%.*/tbrown/Mail/%%')
(head -1 $FILE; \
 echo "X-Msg-Loc: $FOLDER/$NUMBER"; \
 sed '1 d' <$FILE) | /usr/lib/mh/rcvstore +todo

which copies the email to the todo folder with an extra header, X-Msg-Loc, which records the location of the message file relative to the main mail folder, something like 'personal/176'.

Getting todo items from the folder into Leo

The script to import these todo items into Leo is given at the end of this page. It adds nodes to the tree like this:

Subject text 1
  From text 1
  Date text 1
Subject text 2
  From text 2
  Date text 2

And the node with the Subject text headline contains a preview of the email, and python code to re-open the email in your email software when you click the 'run-script' button in Leo.

I just leave the script node sitting at the top of my todo list, and run it with the 'run-script' button so that the new todo items are inserted below the script, at the top of the todo list.

Leo email import script:

# @color
""" Import messages from email todo folder

# get_mail.py $Id$
# Author: Terry Brown
# Created: Tue Sep 05 2006

import email
import mailbox
from email.header import decode_header
import base64
import os

mbox = mailbox.MHMailbox("/home/tbrown/Mail/todo",

importCount = 0

def grok(msg, f):
    """strip non-ascii from head fields"""
    s = decode_header(msg.get(f))[0][0]
    return ''.join([i for i in s if ord(i) < 127])

for msg in mbox:

    if msg.get("Subject").find("FOLDER INTERNAL DATA") > -1:

    importCount += 1

    headline = grok(msg, "Subject")
    fromline = grok(msg, "From")
    dateline = grok(msg, "Date")
    msgloc = grok(msg, "X-Msg-Loc")
    enc = grok(msg, "Content-Transfer-Encoding")

    foundText = False
    html = None
    bodytext = ""
    for part in msg.walk():
        if part.get_content_type().lower() == 'text/plain':
            foundText = True
            bodytext = part.get_payload()[:600]
            if '64' in enc:
                bodytext = bodytext[:bodytext.rfind('\n')]
                bodytext = base64.b64decode(bodytext)
                bodytext = bodytext.replace('\r','')
        if part.get_content_type().lower() == 'text/html':
            html = part

    if not foundText and html:
        bodytext = html.get_payload()[:800]

    # make the bodytext into python by adding #s and add the command to
    # reopen the message at the bottom
    bodytext = bodytext.split('\n')
    bodytext = '\n# '.join(bodytext)
    bodytext += "\nimport os; os.system('claws-mail --select %s')\n" % msgloc

    # create a node after the current one
    nn = p.insertAfter()
    # add the subject, from, date and body text
    nn.scriptSetBodyString("# %s\n# %s\n# %s\n\n# %s" % (headline, fromline,
                                                 dateline, bodytext))
    # set the uA for cleo to recognize this as a todo item
    nn.v.unknownAttributes = {}
    nn.v.unknownAttributes["annotate"] = {'priority': 1}

    # add the from and date fields as child nodes also, so they can
    # be seen in the tree view
    nc = nn.insertAsLastChild()
    nc = nn.insertAsLastChild()

if importCount:

    # move the messages from todo to todo/imported
    os.system("for i in /home/tbrown/Mail/todo/[0-9]*; do "
              + "cat $i | /usr/lib/mh/rcvstore +todo/imported && rm $i; "
              + "done")

g.es("Imported %d messages" % importCount)