I've created a rule to move mail from specified senders to a new mailbox called "News." I'd like my AppleScript to go through the "News" folder and only keep the most recent emaisa from those senders, deleting prior messages. (This is like one of the Sweep functions on MSLive/Outlook.) I've tried writing a script as follows, which is invoked by the rule:
using terms from application "Mail"
on perform mail action with messages these_messages for rule this_rule
tell application "Mail"
set dateToday to current date
set TargetInbox to mailbox "News" of account "iCloud"
set EveryMessage to every message of TargetInbox
repeat with eachMessage in these_messages
repeat with this_message in EveryMessage
set eachSender to the sender of eachMessage
set this_sender to the sender of this_message
set messageDate to date received of EveryMessage
if messageDate < dateToday and eachSender = this_sender then
delete this_message
end if
end repeat
end repeat
end tell
end perform mail action with messages
end using terms from
I see that the script is called by the rule, because of the spinning gear on the menu bar, but none of the desired deleting occurs, and the script seems to hang in some kind of loop.
I'm an AppleScript n00b, do much better with JavaScript. I'd appreciate any help.
Thanks,
Betalantz
macos 10.13.5 | Mail 11.4 | AppleScript 2.7
Best Answer
I don't use Mail.app and haven't much experience implementing Mail actions, so am not in the position to test your script. However, reading through it, there are places in it of potential concern that I would wish to look to first when debugging.
How large is your "News" mailbox ?
There are two features in your script that threaten it to hang or timeout:
1.
set EveryMessage to every message of TargetInbox
If that mailbox has thousands or even hundreds of emails in it, retrieving
every message
as a list of individually referencedmessage
objects (which is what this statement is doing) could take some time. It's almost always way more efficient and less intensive on processor and memory usage to storea reference to
theevery message
object (collection):It has another beneficial feature that I'll address later.
2. Potentially demanding nested
repeat
loopsNested loops are a necessary evil sometimes, but the query about the mailbox size is, again, a source of anxiety for me, especially since its called upon in at the heart of the nest where the bulk of the operations are done. This means that, for each new message that triggers the script, every single message in the mailbox has to be checked and compared to the trigger message.
These are two branches of one issue, really, because if your mailbox only has 5 messages in it, none of this is particularly bothersome. Otherwise, you're first getting AppleScript to retrieve every message in the mailbox and store them in a variable; then cycling through this potentially heavy load and comparing each item to another.
If you had no choice but to use a
repeat
loop whilst knowing that it would have to iterate through a lot of list items (once you exceed about 500 of anything, AppleScript can begin to buckle) it can help, once again, to pass AppleScript a reference to the list of items instead of the actual list. Passing any reference through a script object (which is whata reference to
essentially does), is much, much faster than making AppleScript access an object by other means. Here's an excellent explanation and demonstration of these principles.Simple changes and error corrections
After addressing the heavy issue above, which could turn out to be of little relevance to your particular case, here are one or two de facto elements that are easily changed to either improve the efficiency of the script, or prevent it throwing an error:
1. Placement of variable declarations
This line is currently declared inside the inner
repeat
loop, which also happens to be the the meatier of the two, in general hypothetical terms. However, what this is doing is setting the value of the variableeachSender
to the same value for as many messages you have in your mailbox. That's a lot of wasted operations for AppleScript (a total of the number ofthese_messages
multplied by the number ofEveryMessage
).Instead, move it to here:
Now AppleScript only has to set the variable once for
eachMessage
inthese_message
. That's an efficiency improvement of polynomial time down to linear time!2.
set messageDate to date received of EveryMessage
Initially, I thought you were trying to access the
EveryMessage
object as a reference to the collection I spoke of earlier, and simply didn't realise you couldn't do that in the way you declaredEveryMessage
.But, I now realise it was most likely just your hands typing faster than your thoughts, and introducing an irksome typo.
EveryMessage
here ought to bethis_message
, i.e.:This leads me quite nicely onto my final point, which I hinted at earlier, regarding the other benefit of declaring a variable to an object collection as a reference.
Had you done this:
then this:
would be a perfectly valid piece of AppleScript, which allows you to access the property of every single item in a list IFF done so through a reference to that list before any sort of dereferencing has taken place. Once it's dereferenced, list is evaluated into individual object references, and it will no longer give you the means to enumerate through its properties in that manner.
What you get if done correctly is list containing the values of the
date received
property ofevery message of the TargetInbox
, all through a single line of AppleScript.Currently, as this wasn't how you implemented it, the declaration for
set messageDate
will definitely throw an error.This explains why none of the deletions are occurring, because your script will never successfully reach that part. However, the error would occur immediately, so my feeling is the script isn't even reaching that line either, not even once. Therefore, the problem must originate from a point in the script before it ever successfully enters the inner
repeat
loop. And you already know my hunch about that.Now for a potential solution
I'm sorry that I can't actually test my script to verify that this will definitely work first time without any tweaks, so tell me how you get on with this:
As you can see, I've done away completely with the inner most
repeat
loop thanks to the use of thea reference to
operator. As I declaredeveryMessage
in that manner, I am (hopefully) able to enumerate the properties of all the items the list contains in one go, which should be approximately infinity times faster than having to check the property for each item individually.