TannieSpace

geekery, drawing and then some

Posts about e-mail

Applescripts for OmniFocus: change the context or project of selected tasks.

I use OmniFocus a lot, and have recently tweaked some more AppleScripts to make my workflow even more smooth. I can add any mail to OmniFocus for replies, add confirmation of orders from mail to OmniFocus which will automatically go into my project-shoebox for orders, with a 'waiting for' context and a due date set to 1 week into the future, unless I've changed or added MailTags with a project, a keyword and a due date. In that case it'll take the project, the first keyword and the due date and use them to set up the OmniFocus task. I have a few more scripts:

  • in OmniWeb I can take and add a URL for later reading, it'll go into my inbox by default
  • in OmniWeb I can take and add a URL to my 'wish-list' project (with a start date for at 2 weeks in the future)
  • in OmniFocus I have scripts to easily change the context or the project of selected tasks
  • in Mail I can press a key and have the selected e-mail(s) sent to OmniFocus with a 'Respond to: ' before the subject line, sorting into either a default project or the one specified in MailTags, with the context set to 'mail' and the message URL in the note-field
  • in OmniFocus I can select such a task, press a key to run a script that will open the message and immediately create a reply (I have a separate one for just opening, in case I have to read it thoroughly first)

All these scripts make the integration between all these programs very easy. That and FastScripts. I found useful scripts over at Curtis Clifton's site. His scripts use Growl notification so if you have Growl installed you get a small notification.

I had some trouble putting my own scripts together, so I decided to paste the info here, just in case I need it later on, or someone else runs into similar issues. I found it quite hard to figure out how to add a task to an existing project or to move it from inbox / other project. In the end, it only took a few lines.

First, when I started my script, I wanted a default project or context (I'll use 'project' from now on, but it also applies to the context-script). To do this I created a 'property' at the beginning of the script:

property defaultProject : "Miscellaneous" property alertItemNum : ""

I used the alterItemNum for my alerts through this very simple routine:

on notify(alertName, alertTitle, alertText) display dialog alertText as string with icon 1 end notify

That went at the end of my script.

I started by addressing OmniFocus and 'talking' to the open document:

tell application "OmniFocus" tell front document tell (first document window whose index is 1)

Then, I checked to see if that document had anything selected, and if not, give an error-dialogue:

            set theSelectedItems to selected trees of content
            set numItems to (count items of theSelectedItems)
            if numItems is 0 then
                set alertName to "Error"
                set alertTitle to "Script failure"
                set alertText to "No valid task(s) selected"
                my notify(alertName, alertTitle, alertText)
                return
            end if

If I have no items selected, the script will call the alert routine from above ( my notify(alertName, alertTitle, alertText) ). Because I created a separate routine, I can use this anywhere in the script.

Next, display a dialogue asking for the project: ` display dialog "Change to what Project? " default answer defaultProject buttons {"Cancel", "OK"} default button 2

set theProject to (the text returned of the result) ` The variable 'theProject' will now contain the name of the (new) project to move the tasks too.

Next, the loop to go through all the items and move them:

            set selectNum to numItems
            set successTot to 0
            repeat while selectNum > 0
                set selectedItem to value of item selectNum of theSelectedItems
                set succeeded to my ChangeProject(selectedItem, theProject)
                if succeeded then set successTot to successTot + 1
                set selectNum to selectNum - 1
            end repeat

This loop will end as soon as the variable 'selectNum' (= numItems which I set to the count of the items in the variable SelectedItems) reaches 0. It calls the routine 'ChangeProject' which I'll get to in a bit, but I first want to wrap up this part. Let me also explain the 'if succeeded then set successTot to successTot + 1' line. The line before sets 'succeeded' to the result of the ChangeProject routine. This result can mean anything, numbers, letters, whatever. In this case we use it to return a 'true' or 'false'. That way, we only need to use 'if succeeded' (which will either be 'true' or 'false') as a condition. We don't need to check the actual content of the variable. This can come in handy for various checks, and it took me a while to catch on to that, so I figured I'd mention it here.

To end this main routine, I used:

            set alertName to "General" set alertTitle to "Script complete"
            if successTot > 1 then set alertItemNum to "s"
            set alertText to successTot & " item" & alertItemNum & " changed to Project " & theProject
        end tell
    end tell
    my notify(alertName, alertTitle, alertText)
end tell

Basically, this tells the dialogue box to say the script ran successfully and displays the number of items successfully moved and only to use the 's' after 'item' if the count of successful moves is higher than 1.

That wraps up the main part.

The routine to actually move the task to the other project only took 21 lines:

on ChangeProject(selectedItem, theProject)
    set success to false
    tell application "OmniFocus" to tell default document
        if (theProject is not "") then
            set MyProjectArray to null
            set MyProjectArray to complete theProject as project maximum matches 1
            try
                set MyProjectID to id of first item of MyProjectArray
                set theNewProject to project id MyProjectID
            on error
                set theNewProject to (make project with properties {name:theProject})
            end try

            try
                set newtask to selectedItem
                move newtask to end of tasks of theNewProject
                set success to true
            end try
        end if
    end tell
    return success
end ChangeProject

The first line sets the success to 'false', because we want to actively, after having our success, set it to 'true'. We then chat with OmniFocus again, and this time, we'll just talk to the default document (the one you have open). If the variable 'theProject' is not empty, we'll continue. If it is, the 'if-end if' ends, without having set the 'success' variable to true (and thus, the script failed). You could technically build in an extra loop to change it to a default project, however, I prefer it this way. If I accidentally emptied that dialogue-box, nothing will happen, just as I like it.

If theProject does contain something (anything), the script continues with its routine. It sets the MyProjectArray to null (makes it empty, just in case). It then used OmniFocus complete option to find the first matching project of OmniFocuses project-list. If you've ever only typed the first three letters in one of OmniFocus's project-areas (to set the project) and it magically came up with the right project, this is the same thing. It means you only have to type the first three or four letters of your project, and the script will find it. If it can't find the project, it will create it for you (at the end of your project-list).

After that, it will simply try to add the task to the desired project.


Cleaning up my mailboxes, and useful procmail-bits.

I started decluttering my e-mail. I thought over 1gb worth of e-mail took up too much space, and what the hell did I save in those folders anyway?

I found I also had a huge procmailrc that filtered through my messages and put them in many nested folders.

Before I started I made a small inventory of what I had:

  • work-account, over 600MB in 4000 e-mails
  • private account, over 400MB in 3000 e-mails
  • third fun account, some 25MB worth of mail, nothing really important.

I use IMAP exclusively, and my accounts have about 1GB of space each, so I had the space to save it all. I also have the space to stack my living-room full of boxes, but I'd rather not.

So, I took the necessary steps to clean it up.

Step 1: Less folders!

I decided no more nested folders! This made everything a lot easier, and much cleaner, in mail.app and in mutt. Instant relief. Though I can't and won't go for that 'single archive' approach, I do prefer to have as little folders as possible. Right now I have the default 'Sent' (which I clear out daily) 'Trash' and 'Draft', and for my work account I have the folders 'work' 'social' 'specific mailing-list' 'other mailing-lists'. For my private account I have 'social', 'web', 'license-codes', 'health' and 'finance'. I do receive some social e-mail on my work-account (no rules against that where I work) so I decided to give that a special folder. I made a general and quick separation, based on gut-feeling alone. My work-account receives work-related mailing-lists, but all my other lists go to a third, specifically meant for mailing-list. This way I can disable the account, or simply not read it, if I just want to read my private e-mail.

Step 2: Clearing out old mail.

I went through all my e-mail.

Yes, all of it.

It was Hell.

It was horrible.

It was painful.

I have no clue why I saved so many e-mails that made me feel bad. I tossed them out.

I had e-mails from orders I made, years and years ago. Out they went.

Work related e-mails from problems long solved (talking years here). Delete delete delete.

E-mails with 10MB worth of photos attached. Saved the photos, deleted the attachment from the e-mail if I wanted to keep it, or deleted the e-mail entirely.

I deleted a lot.

I went from having over 1gb worth of e-mail (combined in all three accounts) to about 40mb (combined in all three accounts). I did it last week, and I still feel pretty damn good about it.

Step 3: Perfecting the archive process.

When e-mail from my family arrives, it gets tagged with a special header that MailTags understands. I did this by adding the following rule to my procmailrc:

:0f * ^FROM.*(adres1|adres2) | formail -A 'X-Keywords: family'

This tells procmail that if the mail comes from either 'adres1' or 'adres2' to add a header which says 'X-Keywords: family'. I have a similar rule for mails from friends.

I can then choose to filter it directly to my folder 'social' (for friends and family)

:0 * ^(X-Keywords|X-Mailtags).*(friends|family) .social/

It opens up all kinds of useful tagging!

Then, I looked at my mailing-list mail. I didn't want the hassle of creating a new rule for every list I joined, or to have my procmailrc clog up with old rules, so I looked around and found a great solution:

` :0 * ^X-BeenThere: \/[^@]+ * ! ^(List-Id|X-(Mailing-)?List):.* .$MATCH/

0 * ^((List-Id|X-(Mailing-)?List):(.[< ]\/[^>])) { LISTID=$MATCH

:0
* LISTID ?? ^\/[^@\.]*
.$MATCH/

} `

Both these rules do the same thing, but they act on different headers. Not all mailing-lists use the same method, so I had to use more than one rule. Basically it either looks at the @X-BeenThere@ or @List-Id/X-Mailing-List/X-List@ to see what list the message comes from, and then filters it into a folder with the same name. Excellent!

Step 4: Enjoy!

After a week of using my freshly cleaned mail-accounts, I still feel great about it. Almost everything goes automatically, and I've only had to do minor tweaks so far. With the help of MailActOn I can easily file my e-mails and keep everything nice, clean, sorted and stick to Inbox Zero (which I've done for quite some time, but that archive of mail just kept on bugging me).

Aaaah, the joys of a clean mailbox!