Automatically preview incoming eMails with Quick Look

After reading Murphy Mac’s article about some cool AppleScripts utilizing the Quick Look feature and inspecting the corresponding scripts on Apple’s website, my mind came up with the idea to write an AppleScript for Apple Mail that will automatically show all incoming eMails as a Quick Look preview.

If you also heavily rely on eMail in your business or just like the geeky idea behind it, then I invite you to download my little script:

previmail ¢ automatically preview incoming eMails with Quick Look (version 0.2, ca. 220.1 KB)

An incoming email message displayed by previmail

Requirements
previmail was tested under/with:
¢ Mac OS X 10.5.2
¢ Apple Mail 3.1
¢ Intel & PowerPC based Macs

Installation
To install previmail on your Mac, you must first download it and then create a custom mail rule in Apple Mail. In this mail rule you have to choose to run an AppleScript as shown below:

Apple Mail rule dialog

All rule-matching eMails will then be processed by previmail and displayed using Quick Look.

Background
previmail is a simple rule action script for Apple Mail, that creates a CSS styled HTML version of every matching eMail message. It then saves these HTML versions to single temporary text files and displays them to the user with Quick Look using the «qlmanage -p» command. Finally it deletes all generated temporary files.

It is not possible to directly display an incoming eMail message as an *.emlx file, as the mail rules are executed before the eMail messages get saved as *.emlx files on the hard drive.

Thoughts…
The «qlmanage» command is really interesting for everybody who scripts the Mac. If your scripts needs to display some detailed information, you can just create a HTML page/PDF file/etc. on the fly, save it in the temporary folder and use qlmanage -p to display it. Or think of displaying media like songs, movies, pictures, etc.! And you don’t need to rely on good but external tools like Growl.

Important: Opening and saving the below script code in Script Editor won’t result in a usable AppleScript! That is because previmail internally relies both on a HTML/CSS template file and Python script, which are located inside its Script bundle. Therefor please download the complete script here.

This is the AppleScript code triggered by the Apple Mail rule:


-- created: 19.03.2008
-- modified: 26.03.2008

-- history:
-- v0.2
-- ¢ instead of ugly plain text files, previmail
-- now displays incoming eMail messages using
-- a pretty HTML template
-- v0.1a
-- ¢ initial release

-- Installed together with an AppleScript-based Apple Mail mail rule,
-- this script displays all incoming rule-matching eMails
-- with QuickLook

-- The script was tested under/with:
-- ¢ Mac OS X 10.5.2
-- ¢ Apple Mail 3.1
-- ¢ Intel & PowerPC based Macs

property mytitle : "previmail"
property myversion : "0.2"

-- you can set this property to 'long' to see even more mail infos
-- in the plain text report
property defmsginfo : "short"

-- unique handler to perform AppleScript actions on rule-matching Apple Mail messages
using terms from application "Mail"
	on perform mail action with messages matchmsgs for rule theRule
		my quicklookshow(matchmsgs)
	end perform mail action with messages
end using terms from

(*-- For debugging only...
on run
	tell application "Mail"
		set msgs to (selection as list)
	end tell
	my quicklookshow(msgs)
end run*)

-- main function controlling the script procedure
on quicklookshow(msgs)
	try
		-- this var contains all quoted posix file paths of the generated
		-- temporary files separated by a space
		set strfilepaths to ""
		-- this var contains all paths of the generated temporary files
		set tmpfilepaths to {}
		-- reading the HTML message template from the template file
		set htmptemplatefilepath to ((path to me) as Unicode text) & "Contents:Resources:msg.tpl"
		set htmltemplate to my readfromfile(htmptemplatefilepath)
		repeat with msg in msgs
			-- converting the Apple Mail message to a HTML report
			set msghtml to my msgtohtml(msg, htmltemplate)
			-- finding and getting an unused temp file
			set tmpfilepath to my TmpFile's newpath()
			-- saving the HTML report in the temp file as UTF-8
			my writetofile(msghtml, tmpfilepath)
			-- adding the path of the temporary file to the giant string
			-- which will be passed as an argument to the «qlmanage p» command
			set strfilepaths to strfilepaths & (quoted form of (POSIX path of tmpfilepath)) & space
			-- adding the temporary file to the list of all generated temporary files
			-- as we want to delete them later
			set tmpfilepaths to tmpfilepaths & tmpfilepath
		end repeat
		-- action! displaying the contents of the generated temporary files
		-- to the user with QuickLook!
		set cmd to "qlmanage -p" & space & strfilepaths & ">& /dev/null"
		set cmd to cmd as «class utf8»
		do shell script cmd
		-- deleting the temporary files
		repeat with tmpfilepath in tmpfilepaths
			set cmd to "rm" & space & (quoted form of (POSIX path of tmpfilepath))
			set cmd to cmd as «class utf8»
			do shell script cmd
		end repeat
	on error errmsg number errnum
		if errnum ≠ -1728 then
			set logmsg to ((current date) as Unicode text) & ": " & errmsg & " (" & errnum & ")"
			set logfile to ((path to desktop) as Unicode text) & mytitle & ".log"
			my writetofile(logmsg, logfile)
		end if
	end try
end quicklookshow

-- I am converting an Apple Mail eMail message to a HTML report
on msgtohtml(msg, htmltemplate)
	-- getting infos about the eMail message
	tell application "Mail"
		--set msgid to id of msg
		set msguid to message id of msg
		set msgsize to my getstringsize(message size of msg)
		--set msgmbox to name of mailbox of msg
		set msgcont to my gethtmlcont(content of msg)
		set msgsender to sender of msg
		set msgrecvd to date received of msg
		set msgsent to date sent of msg
		set msgsubj to subject of msg
		set msgtorec to my gettxtrecipients("TO", msg)
		set msgccrec to my gettxtrecipients("CC", msg)
		set msgbccrec to my gettxtrecipients("BCC", msg)
		set msgatms to my gettxtatms(mail attachments of msg)
		set msgreplyto to reply to of msg
	end tell
	-- creating the header, attachment & content part to be inserted into the HTML template
	set msgheader to "<b>From:</b> " & msgsender & "<br />" & "<b>Subject:</b> " & msgsubj & "<br />" & "<b>Date:</b> " & msgsent & "<br />" & "<b>To:</b> " & msgtorec
	if msgccrec is not missing value then
		set msgheader to msgheader & "<br /><b>Cc:</b> " & msgccrec
	end if
	if msgbccrec is not missing value then
		set msgheader to msgheader & "<br /><b>Bcc:</b> " & msgbccrec
	end if
	if defmsginfo is "long" then
		set msgheader to msgheader & "<br /><b>Size:</b> " & msgsize & "<br /><b>Received:</b> " & msgsent & "<br /><b>Reply-To:</b> " & msgreplyto
	end if
	if msgatms is missing value then
		set msgatms to ""
	else
		set msgatms to "<p class=\"atms\"><img src=\"file:///Applications/Mail.app/Contents/Resources/Attachment.tiff\">" & msgatms & "</p>"
	end if
	-- inserting the report parts into the HTML template
	set msgtags to {"$msgheader$", "$msgatms$", "$msgcont$"}
	set msginfos to {msgheader, msgatms, msgcont}
	set msghtml to my tagstoinfos(htmltemplate, msgtags, msginfos)
	return msghtml
end msgtohtml

-- I am replacing found tags in a text with the corresponding infos
-- I am a silly, but working template engine :)
on tagstoinfos(txt, tags, infos)
	set counttags to length of tags
	repeat with i from 1 to counttags
		set tag to (item i of tags) as Unicode text
		set info to (item i of infos) as Unicode text
		set txt to my searchnreplace(tag, info, txt)
	end repeat
	return txt
end tagstoinfos

-- I am a very old search & replace function, that my master
-- Martin still likes to use
-- This time I am working as a slave for the template engine
-- above
on searchnreplace(searchstr, replacestr, txt)
	considering case, diacriticals and punctuation
		if txt contains searchstr then
			set olddelims to AppleScript's text item delimiters
			set AppleScript's text item delimiters to {searchstr}
			set txtitems to text items of txt
			set AppleScript's text item delimiters to {replacestr}
			set txt to txtitems as Unicode text
			set AppleScript's text item delimiters to olddelims
		end if
	end considering
	return txt
end searchnreplace

-- I am returning the recipients of an eMail message with HTML decoration
on gettxtrecipients(rectype, msg)
	-- I am returning the recipeints as a text list
	-- If there are no recipients availabe, I return «missing value»
	set textrecipients to ""
	tell application "Mail"
		if rectype is "TO" then
			set recpnts to to recipients of msg
		else if rectype is "CC" then
			set recpnts to cc recipients of msg
		else if rectype is "BCC" then
			set recpnts to bcc recipients of msg
		end if
		if recpnts is {} then
			return missing value
		else
			set countrecpnts to length of recpnts
			repeat with i from 1 to countrecpnts
				set recaddress to address of (item i of recpnts)
				set recname to name of (item i of recpnts)
				-- sometimes the «name» property is not available for a recipient,
				-- so we have to use an ugly try...end try-block below:
				if i is equal to countrecpnts then
					try
						set textrecipients to textrecipients & recname & " <<a href=\"mailto:" & recaddress & "\">" & recaddress & "</a>>"
					on error
						set textrecipients to textrecipients & " <a href=\"mailto:" & recaddress & "\">" & recaddress & "</a>>"
					end try
				else
					try
						set textrecipients to textrecipients & recname & " <<a href=\"mailto:" & recaddress & "\">" & recaddress & "</a>>, "
					on error
						set textrecipients to textrecipients & " <<a href=\"mailto:" & recaddress & "\">" & recaddress & "</a>>, "
					end try
				end if
			end repeat
			return textrecipients
		end if
	end tell
end gettxtrecipients

-- I am returning the eMail attachments with HTML decoration
on gettxtatms(atms)
	set textatms to ""
	if atms is {} then
		return missing value
	else
		set countatms to length of atms
		tell application "Mail"
			repeat with i from 1 to countatms
				set atm to item i of atms
				set atmname to name of atm
				set atmsize to my getstringsize(file size of atm)
				set atmmime to MIME type of atm
				set atmdl to downloaded of atm
				set atmentry to "«" & atmname & "» (" & atmsize & ")"
				if i is equal to countatms then
					set textatms to textatms & atmentry
				else
					set textatms to textatms & atmentry & ",<br />"
				end if
			end repeat
		end tell
		return textatms
	end if
end gettxtatms

-- I am returning the body/content of an eMail message in HTML format
on gethtmlcont(msgcont)
	set htmlcont to ""
	set msglines to paragraphs of msgcont
	repeat with msgline in msglines
		-- ignoring empty or blank lines
		if length of msgline is not equal to 0 then
			if character 1 of msgline is not (ASCII character 202) then
				set msgline to "<p class=\"cont\">" & msgline & "</p>"
				set htmlcont to htmlcont & msgline
			end if
		end if
	end repeat
	return htmlcont
	log htmlcontent
end gethtmlcont

-- I am returning the given byte site in human readable form
on getstringsize(bytesize)
	set pyscriptpath to ((path to me) as Unicode text) & "Contents:Resources:Scripts:utl.py"
	set cmd to "python " & quoted form of (POSIX path of pyscriptpath) & space & bytesize
	set cmd to cmd as «class utf8»
	set shellresult to do shell script cmd
	return shellresult
end getstringsize

-- I am writing given content to a given file using UTF-8 text encoding
on writetofile(cont, filepath)
	try
		set openfile to open for access filepath with write permission
		set eof of openfile to 0
		set BOM_UTF8 to ((ASCII character 239) & (ASCII character 187) & (ASCII character 191))
		write cont to openfile as «class utf8»
		close access openfile
		return true
	on error
		try
			close access openfile
		end try
		return false
	end try
end writetofile

-- I am reading the content of a given file(path) and return it in UTF-8 text encoding
on readfromfile(filepath)
	try
		set openfile to open for access filepath
		set filecont to read openfile as «class utf8»
		close access openfile
		return filecont
	on error errmsg number errnum
		try
			close access filecont
		end try
		error errmsg number errnum
	end try
end readfromfile

-- script object to manage a temporary file
script TmpFile
	property filepath : missing value
	
	-- I am creating a new, not yet existing file path in the temp folder
	on newpath()
		set tmpfolderpath to (path to temporary items folder from user domain) as Unicode text
		repeat
			set rndnum to random number from 1000 to 99999
			set tmpfilepath to (tmpfolderpath & "eMail_" & (rndnum as Unicode text) & ".html")
			try
				set tmpfilepath to tmpfilepath as alias
			on error
				set filepath to tmpfilepath
				exit repeat
			end try
		end repeat
		return filepath
	end newpath
	
	-- I am returning the file path of the temporary file 
	on getpath()
		return filepath
	end getpath
	
	-- I am trying to delete the temporary file using the Finder
	on remove()
		try
			tell application "Finder"
				delete (filepath as alias)
			end tell
		end try
	end remove
end script