AppleScript – Writing Mail Rule to Keep Most Recent Emails

applescriptemailmail-rulesmail.app

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 referenced message 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 store a reference to the every message object (collection):

set EveryMessage to a reference to every message of the TargetInbox

It has another beneficial feature that I'll address later.

2. Potentially demanding nested repeat loops

Nested 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 what a 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

set eachSender to the sender of eachMessage

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 variable eachSender 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 of these_messages multplied by the number of EveryMessage).

Instead, move it to here:

repeat with eachMessage in these_messages
    set eachSender to the sender of eachMessage  # ...now it's here
    repeat with this_message in EveryMessage
        # It was here...
        .
        .
        .
    end repeat
end repeat

Now AppleScript only has to set the variable once for eachMessage in these_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 declared EveryMessage.

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.:

set messageDate to date received of this_message

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:

set EveryMessage to a reference to every message of the TargetInbox

then this:

set messageDate to date received of EveryMessage

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 of every 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:

    using terms from application "Mail"
        on perform mail action with messages these_messages
            tell application "Mail"
                set everyMessage to a reference to messages in mailbox "News" of account "iCloud"
                repeat with eachMessage in these_messages
                    delete (everyMessage where ¬
                        the date received is not the (current date) ¬
                        and its sender is the sender of eachMessage)
                end repeat
            end tell
        end perform mail action with messages
    end using terms from

As you can see, I've done away completely with the inner most repeat loop thanks to the use of the a reference to operator. As I declared everyMessage 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.

Related Question