Mail's Broken Reply/Redirect/Forward AppleScript Support ¬

2007-08-07

I use the excellent spamtrainer tool on Mac OS X Server mail servers so that I and others can more easily spamassassin what’s spam or not. Up until now I’ve been content to do the following to manually redirect the messages to the junkmail account:

  1. Select the first message in my Junk folder
  2. Cmd-Shift-E (redirect)
  3. “junkm”-Tab (to select my junkmail@domain.tld address)
  4. Cmd-Shift-D (deliver)
  5. Down arrow
  6. If that wasn’t the last message then goto step 2
  7. Else, Cmd-A (select all) and delete/backspace

However, even with spamassassin ripping out most of the spam upfront, there can still be a considerable amount of spam to redirect on Monday mornings and I really don’t need RSI from redirecting spam.

Since I had already tackled some additions to a John Gruber AppleScript a couple weeks ago, I figured I could reasonably solve this problem with an AppleScript. So I whipped out Script Editor, the dictionary for Mail, and the example scripts in /Library/Scripts/Mail\ Scripts/ and started to attack the problem. The only issue: I kept getting “ undefined” errors where the redirect command was supposed to be returning an outgoing message.

Naturally, I figured I must be doing something wrong since I’m not an AppleScripter. After a quick search on macosxhints.com I found an AppleScript to batch-redirect email in Mail.app, but I kept getting the same error with it. I hit up Google, Apple’s AppleScript-users mailing list archives, and MacScripter’s forums and dug out some very unfortunate news: there is “a bug in Mail evidently prevents it from returning a reference (as it should) to a new message created using the forward, redirect or reply commands” and it’s been there for a while (since the release of Tiger).

Verifying the Bug

I had pretty much found all the proof I needed to start getting discouraged, but I had to flesh it out and understand it so as not to be more discouraged… or maybe to further discourage myself? Either way, I developed the following script to test the cases:

using terms from application "Mail"
	on perform mail action with messages selectedMsgs
		tell application "Mail"
			set selCount to (count of selectedMsgs)
			repeat with counter from 1 to selCount
				set msg to item counter of selectedMsgs
				-- try to create a new message
				set newMsg to make new outgoing message with properties {subject:"New AppleScript-genereated Message", visible:true}
				-- try to reply to a message
				set newReplyMsg to reply msg with opening window
				tell newReplyMsg
					set subject to "(AppleScript-generated)" & msg's subject
				end tell
				-- try to forward a message
				set newForwardMsg to forward msg with opening window
				tell newForwardMsg
					set subject to "(AppleScript-generated) " & msg's subject
				end tell
				-- try to redirect a message
				set newRedirectMsg to redirect msg with opening window
				tell newRedirectMsg
					set subject to "(AppleScript-generated) " & msg's subject
				end tell
			end repeat
		end tell
	end perform mail action with messages
end using terms from
-- this is required when _not_ running from the Script menu (e.g. Script Editor, FastScripts, etc.)
using terms from application "Mail"
	on run
		tell application "Mail" to set sel to selection
		tell me to perform mail action with messages (sel)
	end run
end using terms from

If you select one or more messages in Mail and run that script (you can shuffle around the order of creating the reply, forward, or redirect messages if you like) you’ll see the following:

  1. A new, blank message window with the subject “New AppleScript-genereated Message”
  2. A reply to the first message selected
  3. The following error in Script Editor: “The variable newReplyMsg is not defined.”

In Script Editor’s Event Log you’ll see the following:

tell application "Mail"
	get selection
		{message id 768388 of mailbox "Junk" of account "Morgan"}
	make new outgoing message with properties {subject:"New AppleScript-genereated Message", visible:true}
		outgoing message id 248907776
	reply message id 768388 of mailbox "Junk" of account "Morgan" with opening window
		"The variable newReplyMsg is not defined."

The interesting bit is that outgoing message id 248907776 was returned for the call to make new outgoing message with properties, so at least that works correctly.

I wrote the following script to attempt to peek at the id of each outgoing message in the hopes that I might just be able to set the response from redirect/reply/forward manually:

using terms from application "Mail"
	on run
		tell application "Mail"
			repeat with counter from 1 to (count of outgoing messages)
				display dialog "Outgoing message #" & counter & "'s ID #: " & id of item counter of outgoing messages
			end repeat
		end tell
	end run
end using terms from

Apparently Mail considers a message viewer window to be an outgoing message because this script will show the id of the selected message in an open message viewer window. Also, If you leave the new message created with the first script (i.e. the one created by the call to make new outgoing message with properties) open it’ll show the id of that message. However, if you use either a script or manually reply/forward/or redirect a message you’ll get the following error in Script Editor: “Mail got an error: NSReceiverEvaluationScriptError: 3”.

It’s starting to really get interesting, huh!

H3. The Hypothesis

By this point I’ve pretty well confirmed that not only do the reply, forward, and redirect commands not return an outgoing message as they’re supposed to, it appears that they don’t even have ids set. We also know that scripts that relied on these commands worked prior to Mac OS X 10.4 (Tiger).

So what changed in Tiger that might play a part in causing this bug? Well, Apple switched mail from storing all messages for a folder in .mbox format to storing each individual message in their own .emlx file so that Spotlight could more efficiently/easily index those messages.

Is Mail may not generating an id for the new messages because it hasn’t saved them to disk yet? I believe so. And in the case of calling make new outgoing message with properties directly, you’re generating the object right then and there so it gets an id.

Workarounds?

To get around this I can just create a new message and fill in all the requisite properties, right? Almost.

Unfortunately, an outgoing message object contains only a subset of the properties that a message object contains. Most importantly, you have no way to pass the headers from the original message into a new outgoing message which is vital in the case of redirecting messages for training spamassassin as it inspects the full headers. Also, you lose Mail’s tracking of what messages were replied to, forwarded, or redirected, and linking them together (a handy feature to have to give up).

In some cases one can simply make a Rule in Mail to reply, forward, or redirect, but that won’t work in my case. It looks like I’ll have to do this using GUI scripting and have to watch all the messages pop up, fill in, and then disappear.

I do hope this at least helps bring this issue out into the spotlight a little more since it’s existed for so long. I also hope to save someone a lot of wasted effort.

  Textile help