How to get the paths of mailboxes in Apple Mail?

Hi,

I try to collect all IMAP Folders in Apple Mail and I want the complete folder paths. With this code I get all the references to the mailboxes:

tell application "Mail"
	set folders to mailboxes
	repeat with theAccount in accounts
		set folders to folders & mailboxes of theAccount
	end repeat
	return folders
end tell

The entries don’t have a property with the path, but Script Debugger shows me this result. How to get the highlighted part of the objects with AppleScript?

Each mailbox can get parent mailbox.
So, you can go up to root.

Thanks, I already tried it, but building a list of 286 folders is quite slow with 4 seconds. Maybe this can be optimized? I thought it would be faster to access the information which seems to be already there.

tell application "Mail"
	set folders to mailboxes
	repeat with theAccount in accounts
		set folders to folders & (mailboxes of theAccount)
	end repeat
	set allPaths to {}
	repeat with theFolder in folders
		set thePath to ""
		set subFolder to theFolder
		repeat
			try
				set thePath to name of subFolder & "/" & thePath
				set subFolder to container of subFolder
			on error
				exit repeat
			end try
		end repeat
		try
			get name of account of theFolder
		on error
			set thePath to "Local/" & thePath
		end try
		set allPaths to allPaths & {characters 1 thru -2 of thePath as string}
	end repeat
end tell

Execution speed depends on how many messages and mailboxes and the depth of each mailboxes.

My Mail.app env store hundreds of mailing lists.
My M1 Mac mini + macOS 13.6 env’s result with 286 mailboxes:

Your logic (+ little fix): 32.01 sec
My logic : 29.01 sec
Your logic (+ full speed update): 19.32

These scripts calculate full Mail box path from selected mailboxes.

This is not a strict benchmark. But after some times execution, this trend is reasonable for me.

Generally speaking, your script is not so slow.

Thanks for the improvements, Piyomaru. That’s great.

Why does storing the arrays in a script speed up the execution?

Reading huge list take a long time.
This figure is from my book “AppleScript speed up technics”

Direct list access take a long time. And indirect list access is faster than direct.
I don’t know about the mechanism. AppleScript’s array (list) can store various object.
So, its address calculation took a long time, I think.

We experienced this phenomenon in Classic MacOS era (a reference to).
Storing list in script object is new technic in Mac OS X era.

Normal list access script took 15 secs with my M1 Mac mini

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property aList : {}

set a1Dat to current application's NSDate's timeIntervalSinceReferenceDate()

set aList to {}
repeat with i from 1 to 30000
	set the end of aList to i
end repeat

repeat with i in aList
	set aStr to i as string
end repeat

set b1Dat to current application's NSDate's timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat)
--> 15.198549985886

Indirect list access (a reference to) took 0.44 secs

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property aList : {}

set a1Dat to current application's NSDate's timeIntervalSinceReferenceDate()

set aList_ref to a reference to aList

set aList to {}
repeat with i from 1 to 30000
	set the end of aList_ref to i
end repeat

repeat with i in aList_ref
	set aStr to i as string
end repeat

set b1Dat to current application's NSDate's timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat)
--> 0.439829945564

property in script object is the fastest access. It took 0.39 secs

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

script hgs
	property aList : {}
end script

set a1Dat to current application's NSDate's timeIntervalSinceReferenceDate()

set (aList of hgs) to {}
repeat with i from 1 to 30000
	set the end of (aList of hgs) to i
end repeat

repeat with i in (aList of hgs)
	set aStr to i as string
end repeat

set b1Dat to current application's NSDate's timeIntervalSinceReferenceDate()
set c1Dat to (b1Dat - a1Dat)
--> 0.38650393486
2 Likes

Thanks a lot for sharing this. Very interesting.

That’s great info, thanks. The speed difference is indeed huge in your example.

Could reproduce the same results on my side, too.

I never knew about it. It also made me wonder why I never experienced any kind of noticeable delay when iterating through lists using the direct access.

I guess I just never had to deal with lists that contain thousands (let alone tens of thousands) of objects.

I ran some additional tests and it looks like that humanly noticeable difference in speed starts from about 4000 objects in your example.

Developer of the MsgFiler application for macOS here. There’s a much faster way to access the full path of mailboxes without having to go through the mailbox container hierarchy. It involves deliberately throwing an error when accessing a mailbox. The error message contains the full path to the mailbox.

Check out the following code which iterates through mailboxes in all accounts and the local On My Mac account.

tell application "Mail"
    set theList to {}
    set {TID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, quote}
    
    -- All Accounts	
    repeat with a in accounts
        repeat with m in mailboxes of a
            try
                m as rich text
            on error e
                set mailboxName to text item 2 of e
                set theList to theList & mailboxName
            end try
        end repeat
    end repeat

    -- On My Mac	
    repeat with m in mailboxes
        try
            m as rich text
        on error e
            set mailboxName to text item 2 of e
            set theList to theList & mailboxName
        end try
    end repeat

    set AppleScript's text item delimiters to TID

    return theList
end tell
4 Likes

Late thanks for this suggestion. It much faster and less complicated.

May I ask you, how you move the selection within mail in MsgFiler? I tried it with AppleScript without success. Well, I messed around with set selected messages of message viewer 1 to ….

Hi.

It would be even faster to throw just one error for each list of mailboxes:

set theList to {}

tell application "Mail"
	set accountsMailboxes to accounts's mailboxes
	set macMailboxes to its mailboxes
end tell

set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to quote

-- All accounts.
repeat with a in accountsMailboxes
	try
		a as text
	on error e
		set tis to e's text items
		repeat with i from 2 to (count tis) by 6
			set my theList's end to my tis's item i
		end repeat
	end try
end repeat

-- On My Mac.
try
	macMailboxes as text
on error e
	set tis to e's text items
	repeat with i from 2 to (count tis) by 4
		set my theList's end to my tis's item i
	end repeat
end try

set AppleScript's text item delimiters to TID

return theList

Wohooo, that’s really fast. Thanks for sharing.

And here for interest is an alternative hack which seems to work. It’s intermediate in speed between Adam’s suggestion and my development of it and doesn’t use as much code:

tell application "Mail"
	set accountsMailboxes to accounts's mailboxes
	set macMailboxes to its mailboxes
end tell

set theList to {}
repeat with a in my accountsMailboxes
	repeat with m in a
		set my theList's end to (m as record)'s «class seld»
	end repeat
end repeat
repeat with m in my macMailboxes
	set my theList's end to (m as record)'s «class seld»
end repeat

return theList

That’s great und looks clean. How to access the “from” property with the account id? This does not work:

set my theList's end to (m as record)'s from

I don’t know. :slightly_smiling_face: But it’s not necessary. The account ids can be got by normal means. Something like this:

tell application "Mail"
	set {accountIDs, accountsMailboxes} to accounts's {id, mailboxes}
	set macMailboxes to its mailboxes
end tell

set theList to {}
repeat with i from 1 to (count accountIDs)
	set suffix to " of account id " & accountIDs's item i
	repeat with m in my accountsMailboxes's item i
		set my theList's end to (m as record)'s «class seld» & suffix
	end repeat
end repeat
repeat with m in my macMailboxes
	set my theList's end to (m as record)'s «class seld»
end repeat

return theList

Ah, thanks. The solution was probably too obvious for me. :grin:

(Sorry, bit of a newby to Apple Scripting). I’ve tried this script, but the error message I get seems to be entirely different from the one you have been processing. I get

Can’t make {«class asDB» id (application “Mail”) of «class mbxp» “Drafts” of «class mact» id “83C1508D-AEC1-47AB-9367-52F7F2E3AD16” of application “Mail”, «class asDB» id (application “Mail”) of «class mbxp» “INBOX” of «class mact» id “83C1508D-AEC1-47AB-9367-52F7F2E3AD16” of application “Mail”, «class asDB» id (application “Mail”) of «class mbxp» “Sent Messages” of «class mact» id “83C1508D-AEC1-47AB-9367-52F7F2E3AD16” of application “Mail”} into type text.

I’m using Apple Mail 14.0 on Big Sur 11.6.7.

I can see that the path is inside the mailbox object, but (1) it is only the path to the outer folder, not the actual mailbox, and (2) I can’t get it to work - I tried the fragment below, and it says it can’t get the container.

set theList to {}

tell application "Mail"
	set accountsMailboxes to accounts's mailboxes
	set macMailboxes to its mailboxes
end tell

set TID to AppleScript's text item delimiters
set AppleScript's text item delimiters to quote

-- All accounts.
repeat with a in accountsMailboxes
	repeat with b in a
		set y to container of b
		set z to account of container of b
		set x to POSIX path of account of container of b
	end repeat

Finally, and sorry it is such a newb question, but in the later examples in this question, what does <> do, because I can’t find references to that in the scripting docs?

Many thanks.

Hi @kulath.

The scripts above return POSIX-like paths to the mailboxes within the Mail application and work with Mail 16.0. Do they not work with Mail 14.0 or are you trying to get the paths to where the mailboxes are stored on disk?

As far as I can see, Mail itself will only return the disk folder for an account. To get it, you need to get the account’s account directory, which is returned in the form of a file specifier. If you want it as a POSIX path, you have to get this as a separate operation (ie. not included in the specifier that gets the file from the account, because a POSIX path isn’t a Mail object).

set x to account directory of account of container of b
set x to POSIX path of x

-- Or:
set x to POSIX path of (get account directory of account of container of b)

-- Not:
set x to POSIX path of account directory of account of container of b

In my single-account set up, the account folder only contains subfolders for five default mailboxes. The “On my Mac” folders are in a sibling folder to the account folder which isn’t the “MailData” one.

I presume you mean the chevron characters “«” and “»”. They and whatever they enclose represent tokens that haven’t been given corresponding keywords — usually because they’re not intended to be used in scripts! In the scripts above, the hack of coercing Mail’s mailbox objects to records happens to work and returns records that have such tokens as property labels. Hope this makes sense.