Bulk-Export Mails from Apple Mail, Part II

Apple’s mail client has an annoying limitation: If you want to export mail messages as .eml or .emlx you have to do it one by one, i.e. dragging one message at a time from the Mail App’s window to a Finder window. It won’t let you drag several messages at the same time.

applemail-icon

However with a small script this limitation can be circumvented easily. (This is Part II of the other Mail Export post.)

As you may have noted, the intro text above is the same as on my other post about bulk-exporting mails. The script here in Part II however is quite different.

The differences:

  • The script from Part I exports the messages as .emlx, the script here exports as .eml. See below for the differences between of the two formats.
  • The script from Part I is a Bash shell script, while the script here is an AppleScript.
  • The script here is more convenient to use. (No need to copy the messages to an Export box, just select them and run the script.)
.eml or .emlx ?
  • .eml is almost identical to .emlx. .emlx contains a couple of lines more metadata. The content is the same.
  • .eml is a standardized format. If you want cross-platform compatibility, then you have to go .eml. .emlx is Apple-only.
  • .eml is not transparent to Spotlight, i.e. the contents of exported .eml messages will not appear in your Spotlight searches. Technically spoken: macOS does not include an mdimporter for .eml files, only for .emlx. However, there are mdimporters available, provided by third-party software, like EagleFiler. So, if you do have an mdimporter for .eml installed, you shouldn’t have to worry about Spotlight.

So, if you’re fine with .eml, then go with the script here, if you want to export as .emlx, see my other Mail Export script.

Usage:

  1. In Mail.app select the messages you like to export.
  2. Launch the script in your preferred way.
  3. The script will ask you for the destination of the exported messages. Choose one and hit OK.
    • The selected messages will be exported and named with this pattern:
      <dateTtime> <sender mail address without TLD> <subject line>.eml1
      Once the export has finished you will receive a popup with some stats.

Well, basically, that’s all.

OK, some more:

  • If you you don’t want to be asked each time for the destination, you can set a fixed destination in the script. The script is already prepared for this:
    • At the beginning of the script you find some explanatory comments as well as some examples. Uncomment one of the example lines and and adapt it to your needs.
  • The maximum file name length on macOS is 255 chars. However, decomposed non-ASCII-chars (ü, ö, é, ñ, etc.) count twice. This means, if you get an error, because you have lots of non-ASCII chars in one of the subject lines, try to reduce the maximum file name length. (I have set it to 180, which should leave enough room for weird subject lines.) You find the setting around line 86 in the script (and also an explanatory comment there).

Download (1.0.0 / 2017-03-29)

Below you find the content of the script. Please do not copy the source code from here, instead use the download link above, to make sure you get the latest version.


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set exportDestination to missing value

# If you like, set your export folder here (HFS notation = separate path components with a colon “:” + trailing “:”)
# If you don’t set a folder here the script will ask you each time for the destination.
# Some examples:
--set exportDestination to (path to desktop) & "Exported Mails:" as text
--set exportDestination to (path to documents folder) & "My Mails:" as text
--set exportDestination to (path to home folder) & "My Backup Folder:Exported Mail Messages:" as text

set fieldSeparator to space
set msgSubjects to {}
set msgDates to {}
set msgSenders to {}
set msgSources to {}
set msgCount to 0

# Ask for destination (if no path is set at the beginning of the script)
if exportDestination is missing value then set exportDestination to chooseFolder() as text

# Start stop watch
set timeStart to current date

# Grab the messages
tell application "Mail"
	set selMsgs to the selected messages of the front message viewer
	if selMsgs is missing value then
		my alertNoSelection()
		error number -128
	end if
	repeat with theMsg in selMsgs
		set {end of msgSubjects, end of msgSenders, end of msgDates, end of msgSources} to {subject of theMsg, sender of theMsg, date received of theMsg, source of theMsg}
	end repeat
end tell

# Write messages to file
repeat with i from 1 to count of msgSubjects
	set {theSender, theSubject, theDate, theSource} to {item i of msgSenders, item i of msgSubjects, item i of msgDates, item i of msgSources}
	set saveTID to AppleScript's text item delimiters
	# Format sender
	# Full email address
	--set AppleScript's text item delimiters to {"<", ">"}
	# Email address without TLD
	set AppleScript's text item delimiters to {"<", "."}
	set theSender to text item -2 of theSender
	# Format date/time as short ISO
	set AppleScript's text item delimiters to {""}
	set theDate to theDate as «class isot» as string
	set theDate to words 1 thru -2 of theDate as text
	set theDate to text 3 thru -1 of theDate
	# Format subject
	# ':' is the only forbidden character on macOS. You can add more characters if you want, for example '{".", "?", "/", "<", ">", "\", "|"}'
	set AppleScript's text item delimiters to {":"}
	set theSubject to every text item of theSubject
	set AppleScript's text item delimiters to {"-"}
	set theSubject to theSubject as text
	set AppleScript's text item delimiters to saveTID
	# Compose file name
	set msgFileName to theDate & fieldSeparator & theSender & fieldSeparator & theSubject
	# Limit file name length
	# Max is 255, but decomposed characters like ü, ä, é etc. count as two charcters (UTF-8 NFD!); so we better have some margin.
	if (count of msgFileName) > 180 then set msgFileName to text 1 thru 180 of msgFileName
	set msgFileName to msgFileName & ".eml"
	# Save to file
	try
		set newFile to (open for access file (exportDestination & msgFileName) with write permission)
	on error errMsg number errNum
		if errNum is -43 then
			alertDestination(exportDestination, errNum, errMsg)
		else if errNum is -1410 then
			alertNameLength(msgFileName, errNum, errMsg)
		else
			alertOther(errNum, errMsg)
		end if
		error number -128
	end try
	write theSource to newFile
	close access newFile
	set msgCount to msgCount + 1
end repeat

# Read stop watch
set timeElapsed to (current date) - timeStart

# Display stats
display alert "Completed" message "Exported " & msgCount & " message(s) in " & timeElapsed & " seconds to “" & (POSIX path of exportDestination) & "”." as informational

------------------------------------------------------------------------
# Handlers 

on chooseFolder()
	tell application (path to frontmost application as text)
		set dest to (choose folder with prompt "Pick a folder to export your mails to:")
	end tell
	return dest
end chooseFolder

on alertNoSelection()
	display alert "No Message Selected!" message "Select one or more messages in the messages list and run the script again." buttons {"Got it!"}
end alertNoSelection

on alertDestination(dest, eNum, eMsg) # -43
	display alert "Destination folder missing or not accessible" message "Please check the folder path you have set at the beginning of the script or check the write permissions of the destination folder:" & return & return & dest & return & return & "# Error number:" & return & eNum & return & return & "# Error message:" & return & eMsg as critical
end alertDestination

on alertNameLength(fName, eNum, eMsg) # -1410
	display alert "File name too long" message "Because of a very long Subject line the file name of this email message is too long:" & return & return & fName & return & return & "Please lower the file name length limit in the script." & return & return & "# Error number:" & return & eNum & return & return & "# Error message:" & return & eMsg as critical
end alertNameLength

on alertOther(eNum, eMsg)
	display alert "An error has occurred" message "The following error has occurred:" & return & return & "# Error number:" & return & eNum & return & return & "# Error message:" & return & eMsg as critical
end alertOther

Footnotes

  1. I left out the Top Level Domain to squeeze as much useful info as possible into the file name. However, you can easily modify the file naming pattern in the script.