Create iCal to-do from a mail message using AppleScript

I use Things for task management on my mac and iPhone (okay, I’m not as consistent as I’d like, but I’m trying). I simply find the interface cleaner, simpler and aesthetically far superior to the alternatives such as OmniFocus and iGTD. There are disadvantages, though. The pace of development is very slow. I’ve been using the program in beta for a year and version 1.0 was just released. Also, OmniFocus and iGTD (for example) have much better integration with other programs, like Mail. One feature I’ve been waiting for is the ability to create tasks from mail messages. I wanted to be able to select a mail message and automatically create a new task with the subject of the message as the task title and the contents as a note. Unfortunately this feature is still missing in the 1.0 release, so I took matters into my own hands.

Things 1.0 also lacks scripting support. However, the syncing with iCal tasks (via the Leopard to-do server) works very well (see the Things help for how to set that up). iCal is fully scriptable, as is Mail.app. So I created an iCal calendar called “GTD inbox” just for syncing with my Things inbox.

Unfortunately scripting support means AppleScript. I’m allergic to AppleScript. I’ve worked professionally with lots of programming languages, and I usually have a pretty easy time picking them up. AppleScript is possibly the most frustrating and confusing language I’ve ever used, despite (or actually probably due to) being designed to have a human language syntax. I’m also really not interested in knowing AppleScript, since it has limited utility elsewhere. But I googled and stole snippets of code and pasted them together into something that works and does what I want. My initial version gave no user feedback, so I went back and added Growl notification and some rudimentary error handling. This naturally requires that you have Growl installed (but it’s free and you want it anyway).

The script also adds a “message:” url to the task, so you can easily click your way back to the original mail message from the task (also from Things).

So here’s the script:

tell application "GrowlHelperApp"
	set the allNotificationsList to ¬
		{"New Task Notification", "Error adding task"}
	set the enabledNotificationsList to ¬
		{"New Task Notification", "Error adding task"}
	register as application ¬
		"Make todo from mail" all notifications allNotificationsList ¬
		default notifications enabledNotificationsList ¬
		icon of application "Things"
end tell
using terms from application "Mail"
	tell application "Mail"
		set selectedMails to the selection
		repeat with eachMessage in selectedMails
			set theSubject to subject of eachMessage
			set message_url to "message://%3c" & (the message id of eachMessage) & "%3e"
			set theContent to content of eachMessage
			try
				tell application "iCal"
                                       -- Change the calendar name if necessary
					set newtodo to (make new todo at end of todos of calendar "GTD Inbox")
					tell newtodo
						set summary to theSubject
						set url to message_url
						set description to theContent
					end tell
				end tell
				tell application "GrowlHelperApp"
					notify with name ¬
						"New Task Notification" title ¬
						"New task added" description theSubject ¬
						application name "Make todo from mail"
				end tell
			on error errmessg
				tell application "GrowlHelperApp"
					notify with name ¬
						"Error adding task" title ¬
						"Failed to add new task" description errmessg ¬
						application name "Make todo from mail"
				end tell
			end try
		end repeat
	end tell
end using terms from

Modify if you like and then save it in the Script Editor. I put mine in ~/Library/Scripts/Applications/Mail/, but where you put it doesn’t really matter.

Finally, set up a hotkey to run the script. I use Quicksilver (and so should you), so I just created a trigger that runs my script. Now when I select a mail message (or several) and press cmd-å (having a Swedish keyboard means having three letter keys that almost never have other bindings), a task is created from the message and I get a Growl notification as verification.

I also made a modified version of the script that can be run from a mail rule to automatically create tasks from messages when they arrive. Now when my wife sends me a message with GTD somewhere in the subject, the message automatically ends up in my Things inbox. There’s no Growl notification for this one.

using terms from application "Mail"
	on perform mail action with messages theMessages for rule theRule
		tell application "Mail"
			repeat with eachMessage in theMessages
				set theSubject to subject of eachMessage
				set message_url to "message://%3c" & (the message id of eachMessage) & "%3e"
				set theContent to content of eachMessage
				try
					tell application "iCal"
                                                -- change calendar name if necessary
						set newtodo to (make new todo at end of todos of calendar "GTD Inbox")
						tell newtodo
							set summary to theSubject
							set url to message_url
							set description to theContent
						end tell
					end tell
				end try
			end repeat
		end tell
	end perform mail action with messages
end using terms from

Update: Quicksilver has apparently lost the ability to limit the scope of triggers to specific applications (since Leopard). So I installed Spark, which is also free and seems to work well. I also used it to add emacs-like bindings for the arrow keys (ctrl-N, ctrl-P, ctrl-F, ctrl-B) for navigating Things. Quicksilver is still very much worth having, though.

Another Update: The version of Webkit included with the Safari 4 beta breaks at least the second script above (and probably both). The problem seems to occur when html-formatted messages are read in because some method is no longer allowed to be called from a secondary thread. My guess is it’s something that Apple changed on purpose but without realizing that it would break some of Mail.apps AppleScript functions. If it’s still broken after the Safari 4 release, I’ll have to try to find a workaround. Until then, don’t install the beta if you want to use this script.

Tags: , , ,

19 Responses to “Create iCal to-do from a mail message using AppleScript”

  1. den says:

    I found this script from your mention on the support forums. I have it set that I can access it from the script menu along the top tool bar in leopard. It works fine.

    It would be great to be able to forward a mess with a keyword that would trigger the script and move it to things like in omnifocus though.

  2. Johan says:

    @den

    I haven’t used this feature in OmniFocus, but it sounds like what I do with the second script above. You just have to go into the prefererences in Mail and create a rule that runs the script whenever a keyword appears in the subject line. I have mine set up to run the script when ‘GTD’ appears in the subject line.

  3. William says:

    Thanks for this script! I have absolutely no AppleScript experience but I’m trying to learn from existing scripts and modify your script to suit my preferences exactly. Could you help me figure out what I would plug in place of “reply to” if I only wanted the name to export from Mail.app into the subject of the todo? I tried “name” but when I run the script nothing happens which leads me to believe that this is an incorrect “object(?)” Thanks again for this simple but awesome script. I use it in tandem with InDev’s Act-On to get the same capabilities that you have with QuickSilver/Spark.

  4. Johan says:

    @William

    Heh, as I tried to say, I’m the wrong person to ask for AppleScript advice. But let’s see…

    I’m not sure I understood your question. You mean you want the sender’s name to end up as the subject of the todo? The message object has a string property called sender. Presumably that will contain both the name and the mail address, but you could remove the address, I guess.

  5. William says:

    If it’s too complicated then I won’t worry about it. Right now, as the script is written so that the todo is created the “summary” contains both the name and the email address like: “Bob Smith ”

    I just wanted to set the “summary” to say “Bob Smith” and I added the email to the “description” of the todo.

    By the way, is there a simple way to see all the message object string properties that there are? Not just in Mail, but in any app. In other words, how do I find out what objects and string properties are available to manipulate in an application using an applescript?

    You can follow this link to see what I’m trying to do.
    http://snipr.com/9wpmf-vkf7j7

  6. William says:

    Sorry, something happened that stripped my last post of my email example:

    The first paragraph of that last post should have read:
    If it’s too complicated then I won’t worry about it. Right now, the script is written so that when the todo is created the “summary” contains both the name and the email address like: “Bob Smith bobsmithATexample.com”

  7. William says:

    Nevermind, I think I got it.
    Found the answer here:
    http://forums.macosxhints.com/archive/index.php/t-81806.html

    Here is the latest version of what I did:
    http://snipr.com/9wr7w-unsqvr

    Not sure if I need the if then statement (or even if it’s written correctly), but it compiles with it there so maybe one day I’ll come across a blank sender name with an email address only and I’ll see what happens.

    This is the first I’ve ever used AppleScript. It’s really simple and cool. Do you have a suggestion for where I can learn more about it?

  8. Johan says:

    To find what objects and properties an application exposes, open its dictionary in the Script Editor (File->Open Dictionary).

    Try http://www.apple.com/applescript/

  9. Adrian says:

    Hey,

    I tried your second script (the modified version of the original script), and created a rule that would apply the script to email from a particular person. But it refuses to work! Nothing happens at all! I’m using Mail version 3.5… could that be the issue? What else could be wrong?

  10. Johan Swanljung says:

    Really hard to diagnose from afar. You’re sure you created the calendar ‘GTD inbox’ first? It won’t work if the calendar doesn’t exist.

  11. Adrian says:

    Thanks for the reply… hopefully you can help me figure this out. Here’s the thing… I edited the script in Script Editor and excluded everything before and after the TRY section… This is what I left:

    try
    tell application “iCal”
    – change calendar name if necessary
    set newTodo to (make new todo at end of todos of calendar “Work”)
    tell newTodo
    set summary to “My Subject”
    set url to “message_url”
    set description to “My Content”
    end tell
    end tell
    end try

    When I save that, the rule works, and the todo is created (though obviously not using nay info from the mail itself). When I include the rest of the script, even if I don’t modify the script I just wrote about, nothing happens… no to do is created… nothing at all happens…

  12. Johan Swanljung says:

    I’m stumped. I can’t get it not to work. I tried recopying the code from the blog into a new script, but it still worked for me. I have Mail 3.5 as well.

    I don’t know how well you know AppleScript, but you can’t know much less than I do :) . But if you want to debug this, one tip is to put code in the script to write messages to the log. For instance, putting

    do shell script “logger Message count ” & (count theMessages)

    After the “on perform mail action…” line will put a little message in your system log when your rule runs that simply tells you the number of messages in the list theMessages (should be 1 if it’s run as a rule). You can inspect your system log with Console.app (in your utilities folder). If you don’t find anything there, that tells you something went wrong before that. You can then modify what you send to logger to get more information.

    do shell script “logger Todo script started”

    at the beginning of the script (before anything else) will tell you that the script started succesfully.

    do shell script “logger message subject:” & theSubject

    after “set theSubject…” will check to see if that variable is correctly set. And so on.

    So I can’t tell you what exactly why this isn’t working on your machine, but checking what happens as the script is run can help you figure it out.

  13. Adrian says:

    Thanks for that… I’m a real amateur at Applescript btw.

    So i tried the do shell script “logger Message count ” & (count theMessages) after the “on perform mail action…” and nothing appeared in the Console. I also have a trial script debugger, and it executes the first line (using terms from application “Mail”) but stops there (”on perform mail action with messages theMessages for rule theRule” doesnt get executed).

    So now we know whats not executing. But do you have any ideas why. Am i suppose to change anything in that line to match my machine (for instance, am I suppose to leave ‘theRule’ as is?).

    Thanks for your help so far… I really appreciate it.

  14. Johan Swanljung says:

    Unfortunately a debugger won’t help you because the script handler perform mail action with messages won’t be triggered unless you run the script from Mail.app.

    Make sure you have the console set so that you see all messages (click on the left button in the toolbar – mine’s not in English, so I don’t know what it’s called) and not just system messages. Have you tried putting in a log message at the very start of the script (to make sure logging is working)?

  15. Adrian says:

    Tried this:

    do shell script “logger Message count test”
    using terms from application “Mail”
    do shell script “logger Message count test 2″
    on perform mail action with messages theMessages for rule theRule
    do shell script “logger Message count test 3″………….

    Only the first 2 of the 3 log messages appeared in Console. So it’s definitely something in the ‘on perform mail action’ line thats causing the trouble.

  16. Adrian says:

    OK… more info:

    The console messgaes appeared when I ran the script via Script Editor. When I ran the script via a rule, these messages appeared in COnsole:

    Mail[248] An exception was thrown during execution of an NSScriptCommand…
    Mail[248] NSData* -[WebArchive data](WebArchive*, objc_selector*) was called from a secondary thread

    Am I hopeless yet?

  17. Johan Swanljung says:

    Well it definitely won’t run in the script editor, because only Mail.app will call the script handler. “perform mail action with messages” is kind of a trigger that Mail.app uses to pass the message list and the name of the rule to a script. Without Mail.app, this trigger will never be called and so the script in that part will never be called.

  18. Johan Swanljung says:

    Ouch, the errors you got when you ran it as a rule are not really AppleScript errors. NSData is a pointer to a Cocoa data container. WebArchive is a class in the webkit library…

    See what happens if you comment out the line with “set theContent” (put “–” in front of it). How are you testing your rule? I mean what kind of message are you sending. Could something in the content be problematic?

  19. Johan Swanljung says:

    Hey, have you installed the Safari 4 beta? Some people are getting the same error with Growl after installing Safari 4. (with html-formatted messages)

    http://forums.cocoaforge.com/viewtopic.php?f=6&t=19272

    I see. The WebKit included in 10.5.6 doesn’t allow access from auxiliary threads… and our reading of an HTML-formatted message does exactly that, indirectly. I’m not sure what the fix will be yet, but the problem is clear. Thanks.