Here’s a cleaner version of the Mail archiving script I posted recently.
I wrote this to chip away at a mailbox with130,000 messages going back to 2011!
It seems to run OK though not terribly fast.
But if I leave it running overnight, in the morning I find Script Editor is using 30 to 40 GB of app memory (on a computer with 8 GB physical ram) and has to be force quit. Usually it gets through a couple of thousand messages before crashing.
And no I am not selecting all 130,000 messages before running the script!
I know it’s problematic to “set selected_messages to selection” but I can’t find any other way of specifying individual messages in a script. My bash-script brain wants to dump all the message ID"s to a text file and step through it, deleting lines as we go, instead of holding them all in memory. But I couldn’t get that to work.
(*
This script creates a folder called "saved-email" in Documents (with subfolders for each year) and saves each message in its own folder, also saving attachments if any. Message folders & files are named with the sender, subject, and UID of the message to prevent duplication or overwriting. The script also sets the timestamp of each saved message to its date sent, for easy chronological sorting & searching.
Select some subset of old messages in the Mail app and run the script. It works pretty well if you do a couple thousand messages at a time.
This script includes various chunks of other people's scripts that I found here and there. Much gratitude to these valiant partners in the struggle.
*)
set path_to_docs to "~/Documents/saved-email/"
tell current application
repeat with y from 2011 to 2025
do shell script ("mkdir -p " & path_to_docs & y)
end repeat
end tell
tell application "Mail"
set selected_messages to selection
set msg_total to (count selected_messages)
set running_total to 1
repeat with this_message in selected_messages -- loop through the messages sent by Mail
try
set msg_sender to (extract name from (sender of this_message))
if msg_sender = "" then set msg_sender to (extract address from (sender of this_message))
set msg_text to content of this_message -- retrieve message body
set msg_subject to (subject of this_message) as Unicode text
set msg_date to (date received of this_message) as date
set yyyy to (year of msg_date)
set msg_id to id of this_message as string
tell current application
do shell script ("echo " & quoted form of (msg_sender & "-" & (msg_subject & "-" & msg_id)) & " > /tmp/scratch.txt")
set the_file_name to (do shell script "cat /private/tmp/scratch.txt | sed -r 's/Re://g ; s/[^[:alnum:]-]/-/g ; s/-+/-/g ; s/^-//g' ") -- clean up message names
set thePath to path_to_docs & yyyy & "/" & the_file_name & "/"
do shell script ("mkdir -p " & thePath)
do shell script ("touch " & thePath & the_file_name & ".txt")
set save_folder to ((path to documents folder as string) & "saved-email:" & yyyy & ":" & the_file_name & ":") as alias
set disk_file to ((save_folder as string) & the_file_name & ".txt" as string) as alias
set disk_file_id to open for access file disk_file with write permission -- open the new file for writing
write msg_text to disk_file_id -- write the body text of the current email into the new file
close access disk_file_id -- close the file
end tell
if (count mail attachments of this_message) > 0 then
repeat with this_attachment in (mail attachments of this_message)
try
set this_attachment_filename to (((save_folder as string) & this_attachment's name))
save this_attachment in alias this_attachment_filename -- saves the attachment
end try
end repeat
end if
tell application "Finder"
set save_folder's modification date to msg_date
set (entire contents of save_folder)'s modification date to msg_date
end tell
delete this_message
tell current application to do shell script ("echo " & quoted form of ("___________________ Archived " & running_total & " of " & msg_total) & " messages > /dev/null ")
set running_total to running_total + 1
end try
end repeat
end tell
I’ll look at it. BTW why do you have a ‘tell current application’?
It is not needed
Without it, the “do shell script” commands in the middle of the Mail tell block throw errors.
The one I’m referring to is at the top, outside of the mail tell block.
the one inside can use ‘tell me’ instead’
See if this works
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
property mailFile : missing value
on run
set mailFile to (path to desktop folder as text) & "Mail-IDs.txt"
if generateMailFile() then
setupFolders()
processMail()
end if
end run
on generateMailFile()
local mfile, myList
try
set mfile to open for access file mailFile with write permission
on error
return false
end try
set eof mfile to 0
set myList to {}
tell application "Mail"
set selected_messages to selection
repeat with this_message in selected_messages
set this_message to contents of this_message
set aMailbox to mailbox of this_message
set anAccount to account of aMailbox
set tmp to (id of this_message as rich text) & tab & name of aMailbox & tab & id of anAccount & linefeed
tell me to write tmp to mfile
end repeat
end tell
close access mfile
return true
end generateMailFile
on processMail()
local mfile, myList, c, this_message, msg_sender, msg_text, msg_subject, msg_date, yyyy, msg_id, the_file_name, thePath, tid
try
set mfile to open for access file mailFile
on error
return false
end try
set progress description to "Manage Emails…"
set progress total steps to -1
set progress completed steps to 0
set tid to text item delimiters
set text item delimiters to tab
set c to 1
repeat
try
set mailItem to text 1 thru -2 of (read mfile until linefeed)
on error errMsg number errNum
exit repeat
end try
set progress completed steps to c
set mailItem to text items of mailItem
set {mailID, mboxName, accntID} to mailItem
set mailID to mailID as integer
tell application "Mail"
tell mailbox mboxName of account id accntID
--message mailID
set this_message to item 1 of (messages whose id is mailID)
set msg_sender to (extract name from (sender of this_message))
if msg_sender = "" then set msg_sender to (extract address from (sender of this_message))
set msg_text to content of this_message -- retrieve message body
set msg_subject to (subject of this_message) as text
set msg_date to (date received of this_message) as date
set yyyy to (year of msg_date)
set msg_id to id of this_message as string
end tell
end tell
do shell script ("echo " & quoted form of (msg_sender & "-" & (msg_subject & "-" & msg_id)) & " > /tmp/scratch.txt")
set the_file_name to (do shell script "cat /private/tmp/scratch.txt | sed -r 's/Re://g ; s/[^[:alnum:]-]/-/g ; s/-+/-/g ; s/^-//g' ") -- clean up message names
set thePath to path_to_docs & yyyy & "/" & the_file_name & "/"
do shell script ("mkdir -p " & thePath)
do shell script ("touch " & thePath & the_file_name & ".txt")
set save_folder to ((path to documents folder as text) & "saved-email:" & yyyy & ":" & the_file_name & ":") as alias
set disk_file to ((save_folder as text) & the_file_name & ".txt" as text) as alias
set disk_file_id to open for access file disk_file with write permission -- open the new file for writing
write msg_text to disk_file_id -- write the body text of the current email into the new file
close access disk_file_id -- close the file
tell application "Mail"
if (count mail attachments of this_message) > 0 then
repeat with this_attachment in (mail attachments of this_message)
try
set this_attachment_filename to (((save_folder as string) & this_attachment's name))
save this_attachment in alias this_attachment_filename -- saves the attachment
end try
end repeat
end if
end tell
tell application "Finder"
set save_folder's modification date to msg_date
set (entire contents of save_folder)'s modification date to msg_date
end tell
tell application "Mail"
delete this_message
tell current application to do shell script ("echo " & quoted form of ("___________________ Archived " & running_total & " of " & msg_total) & " messages > /dev/null ")
set running_total to running_total + 1
end tell
if (c mod 10) = 0 then
set my progress additional description to subject of mailItem & " (" & c & ")"
end if
set c to c + 1
end repeat
close access mfile
set text item delimiters to tid
end processMail
on setupFolders()
set path_to_docs to (path to documents folder as text)
try
alias ((path to documents folder as text) & "saved-email:")
on error
tell application "System Events" to make new folder at folder path_to_docs with properties {name:"saved-email"}
end try
set path_to_docs to path_to_docs & "saved-email:"
repeat with y from 2011 to 2025
try
alias (path_to_docs & y & ":")
on error
tell application "System Events" to make new folder at folder path_to_docs with properties {name:(y as text)}
end try
end repeat
end setupFolders