Apple Mail - rule & script that saves every mail as .eml on HD?

Hi there,

I need a little help with scripting of Apple Mail.

I’m looking for a script, that is attached to an Apple Mail rule and saves every incoming Mail as .eml on HD.

Can anybody help me with that?

Thank you
Carl

Browser: Safari 605.1.15
Operating System: macOS 10.14

I written 2 scripts for Mail.app rules as far. The first saves chozen message as EML. The second creates mail rule programatically. Understand the idea of this 2 scripts to adapt for your needs:


-- Script: save email as EML file

-- get name and source of chosen message
tell application "Mail"
	set aMessage to item 1 of (get selection)
	set richSource to (source of aMessage) as rich text
	set theSubject to subject of aMessage
end tell

-- replace every ":" symbol in theSubject with "_"
set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ":"}
set {itemList, AppleScript's text item delimiters} to {text items of theSubject, "_"}
set {theSubject, AppleScript's text item delimiters} to {itemList as text, ATID}

-- build destination file's HFS path
set outputFile to (((path to desktop folder) as text) & theSubject & ".eml")

-- write rich text to EML file
try
	set fileReference to open for access file outputFile with write permission
	set eof of fileReference to 0
	write richSource to fileReference
	close access fileReference
on error
	try
		close access file outputFile
	end try
end try

-- Script: make new rule programatically

set ruleConditionAccountConstant to «constant eruttacc»

tell application "Mail"
	tell (make new rule with properties {name:"Delete Apple Support Commubities thread intrusive messages", delete message:true, all conditions must be met:true})
		make new rule condition with properties {rule type:ruleConditionAccountConstant, expression:"imap://kniazidis.rompert%40gmail.com/"}
		set its enabled to true
		set stop evaluating rules to false
	end tell
	save
end tell


Creating the rule programatically you can avoid, using manual way. Steps:

  1. Choose “if all of the following conditions is meat”
  2. Choose “message is addressed to my full name”
  3. Choose “Run Applescript”
  4. Then you should attach to this rule following script, which uses idea of my script 1 (save it to Mail actions scripts location before the attaching):

using terms from application "Mail"
	on perform mail action with messages these_messages for rule this_rule
		tell application "Mail"
			repeat with eachMessage in these_messages
				set richSource to (source of eachMessage) as rich text
				set theSubject to subject of eachMessage
				set outputFile to my buildEMLfilePath(theSubject)
				my writeToEmlfile(richSource, outputFile)
			end repeat
		end tell
	end perform mail action with messages
end using terms from

on buildEMLfilePath(theSubject)
	-- replace every ":" symbol in theSubject with "_"
	set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ":"}
	set {itemList, AppleScript's text item delimiters} to {text items of theSubject, "_"}
	set {theSubject, AppleScript's text item delimiters} to {itemList as text, ATID}
	-- build destination file's HFS path
	set outputFile to (((path to desktop folder) as text) & theSubject & ".eml")
end buildEMLfilePath

on writeToEmlfile(richSource, outputFile)
	-- write rich text to EML file
	try
		set fileReference to open for access file outputFile with write permission
		set eof of fileReference to 0
		write richSource to fileReference
		close access fileReference
	on error
		try
			close access file outputFile
		end try
	end try
end writeToEmlfile

Thank you KniazidisR very much.

Just to check, if I understand it correctly…

The script for creating the rule is just an example to help me build a script with rules that fit my needs?

Cheers

Carl

Yes. Better, make rule manually, to not open one other topic, related to programatically creating the rule and activating it.

Here is one new rule I created using the Mail.app Preferences–>Rules–>Add Rule. As you see I used “Every message” instead of “message is addressed to my full name”.

“ExportAsEMLfile.scpt” is name of the script above (last script of the post #2). I stored it in the Mail action scripts location. On my Mac it is “/Users/123/Library/Application Scripts/com.apple.mail/”


POSIX path of (((path to library folder) as text) & "Application Scripts:com.apple.mail:")

This is a script that I use to save selected email messages in .eml format in Apple Mail. I use it on both incoming and outgoing messages. I haven’t adapted it to work based on a Mail rule on all incoming email, but you may be able to do that. I trigger it with a keyboard shortcut on the emails I want to save. I’d be interested in how you get on with using it with a Mail rule.

A few notes:

  • The script saves the message/s that is selected in the first message viewer window. If you have multiple message viewer windows open, or multiple messages open, you may get unexpected results if you don’t keep that fact in mind that the script will just save the messages that are selected in the first message viewer window.
  • The script uses GUI scripting to save the email. I don’t know any way around this but would love to hear of it if others know more. This means that you’ll need to give accessibility access in System Preferences to whatever app you use to trigger the script. It also means that your Mac will jump around visibly doing something when the script is triggered which might be a bit distracting if you have it set with a Mail rule to trigger on every incoming email.
  • If you use the Small Cubed Mail Suite/MailTags plug in, like I do, the script will put the project name of the message in the file name. If you don’t have the plug in, it should just omit it with no issues.

I’m currently running it on macOS 11.2, but have been using versions of this script for a few years on various versions of macOS including Mojave and earlier.


-- save selected email messages in the Downloads folder
-- Version 1.2, 28 February 2021
-- Copyright 2017 by Nicholas Parsons

-- declare a proprety for where the email message will be saved
property theLocation : missing value

try
	-- set theLocation if it hasn't already been set
	if theLocation is missing value then set theLocation to choose folder with prompt "Choose where you want your emails to be saved."
	-- if the user cancels it should trigger an error number -128 which will end execution of the script
	set theLocation to POSIX path of theLocation
	
	-- try to get the selected messages from the frontmost window of  Mail
	with timeout of 10 seconds
		tell application "Mail" to set selectedMessages to the selected messages of message viewer 1
	end timeout
	
	-- only proceed if some messages have been selected
	if selectedMessages is not missing value then
		
		-- take the first message selected and extract the relevant info
		set theSender to getSender for item 1 of selectedMessages
		set theRecipient to getRecipient for (item 1 of selectedMessages)
		tell application "Mail" to set theDate to the date sent of item 1 of selectedMessages
		set theProject to getProject for (item 1 of selectedMessages)
		set theSubject to getSubject for (item 1 of selectedMessages) given project:theProject
		
	else -- there are no messages selected
		display notification "No email messages selected." with title "Select a message first"
		error number -128
	end if -- there are some selected messages
	
	-- generate a file name for the email
	set fileName to getDateString(theDate) & " Email from " & theSender & theRecipient & " about " & theSubject & ".eml"
	log fileName
	
	saveEmail to fileName
on error errorMessage number errorNumber
	if errorNumber is not -128 then display notification errorMessage & return & return & "Error " & errorNumber with title "Whoops, something went wrong" sound name "Basso"
end try

on getDateString(theDate)
	-- get the day of the given date in a two digit format
	try
		if (day of theDate) is less than 10 then
			set dayString to 0 & (day of theDate as string)
		else
			set dayString to (day of theDate as string)
		end if
	on error
		set dayString to "xx"
	end try
	
	-- get the month of the given date in a two digit format
	try
		if (month of theDate as number) is less than 10 then
			set monthString to 0 & (month of theDate as number)
		else
			set monthString to (month of theDate as number)
		end if
	on error
		set monthString to "xx"
	end try
	
	-- generate a date string in yyyy-mm-dd format
	try
		set dateString to (year of theDate as string) & "-" & monthString & "-" & dayString
	on error
		display notification "Some data that was expected to be a date was not in fact a date." with title "Whoops, something went wrong"
		set dateString to "xxxx-" & monthString & "-" & dayString
	end try
	return dateString
end getDateString

on getSender for thisMessage
	tell application "Mail" to set theSender to extract name from the sender of thisMessage
	log theSender
	return theSender
end getSender

on getRecipient for thisMessage
	tell application "Mail" to tell thisMessage
		-- try to get the name of the first recipient of the email
		if the (count of to recipients) is 1 then
			set theRecipient to the name of the first to recipient
		else -- if there are more than one recipients
			set theRecipient to (the name of the first to recipient) & " et al"
		end if
		log theRecipient
		-- If the recipient was just an email address with no name, it will now be missing value. IN this case, we will leave it as an empty string.
		if theRecipient is missing value then
			set theRecipient to ""
		else -- if there is a recipient, format it for inclusion in the file name
			set theRecipient to " to " & theRecipient
		end if
	end tell
	return theRecipient
end getRecipient

on getSubject for thisMessage given project:theProject
	tell application "Mail" to set theSubject to the subject of thisMessage
	
	-- get rid of any leading "Re:" or "Fwd" from the email subject
	if theSubject is not missing value and theSubject is not "" then
		try
			if text 1 through 5 of theSubject is "Fwd: " then set theSubject to text 6 through -1 of theSubject
			repeat while text 1 through 4 of theSubject is "Re: " or text 1 through 4 of theSubject is "RE: " or text 1 through 4 of theSubject is "FW: "
				set theSubject to (text 5 through -1 of theSubject) as text
			end repeat
		end try
	end if -- theSubject is missing value or empty string
	
	-- if the messsage has a project that isn't referenced in it's subject line, get it to append to the end of the file name
	if theSubject is missing value then
		if theProject is missing value then
			set theSubject to "missing value"
		else -- there's a project
			set theSubject to theProject
		end if -- there's no project
	else if not ((theProject is missing value) or (theSubject contains the last word of theProject)) then
		set theSubject to theSubject & " – " & theProject
	end if -- no subject
	
	log theSubject
	return theSubject
end getSubject

on getProject for thisMessage
	-- get the MailTags project of the email
	tell application "Mail"
		try
			tell application "SmallCubed MailSuite" to set theProject to the project of thisMessage
		on error
			set theProject to missing value
		end try
	end tell
	return theProject
end getProject

on saveEmail to fileName
	-- save the email message
	with timeout of 10 seconds
		log "Saving email"
		tell application "Mail" to activate
		
		-- send commands through System Events for GUI scripting
		tell application "System Events" to tell application process "Mail"
			
			-- trigger save as keystroke
			keystroke "s" using {command down, shift down}
			
			-- wait for the save sheet to appear
			with timeout of 5 seconds
				repeat until sheet 1 of window 1 exists
				end repeat
			end timeout
			delay 0.5
			tell sheet 1 of window 1
				
				-- set the file name
				if the fileName is not "" then set value of text field 1 to the fileName
				delay 0.2
				
				-- set the save in location
				keystroke "g" using {command down, shift down}
				
				with timeout of 5 seconds
					repeat until sheet 1 exists
					end repeat
				end timeout
				
				-- check what version OS is running
				-- In macOS Sierra, the location field is a combo box. In earlier versions, it was a text field.
				tell sheet 1
					if my macOSIsNewerThan("10.12") then
						set the value of combo box 1 to theLocation
					else
						set the value of text field 1 to theLocation
					end if
					click button "Go" -- to set the location
					-- do something to check that the path has been entered correctly and the dialog dismissed
				end tell -- go to sheet
				
				-- Prior to High Sierra, the "Save" button was button 1. In High Sierra, it's no longer button 1, but it is the last UI element
				if my macOSIsNewerThan("10.13") then
					click the last UI element -- to save
				else
					click button 1 -- to save
				end if
			end tell -- sheet 1/save dialog
		end tell -- System Events/Mail
	end timeout
	display notification "Message Saved" with title "The email message has been saved in Downloads folder." sound name "Glass"
end saveEmail

on macOSVersion()
	set theInfo to system info
	set theVersion to system version of theInfo
	-- when running this script in ScriptDebugger system info still reports macOS 11 Big Sur as version 10.16
	-- but when running it from KM (I think) it reports it as 11.x
	return theVersion
end macOSVersion

on macOSIsNewerThan(specifiedVersion)
	set usersVersion to macOSVersion()
	considering numeric strings
		return usersVersion is greater than or equal to specifiedVersion
	end considering
end macOSIsNewerThan

(* change log

##New in Version 1.2##

* updated for compatibility with macOS Big Sur
* refactored

*)