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:
- Select the first message in my Junk folder
- Cmd-Shift-E (redirect)
- “junkm”-Tab (to select my junkmail@domain.tld address)
- Cmd-Shift-D (deliver)
- Down arrow
- If that wasn’t the last message then goto step 2
- 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 “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:
- A new, blank message window with the subject “New AppleScript-genereated Message”
- A reply to the first message selected
- 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 id
s 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.