I was often asked, if I could provide a simple solution to print out incoming eMails and attachments in Apple Mail. Well, so far I couldn’t, as Apple Mail unfortunately doesn’t support to automatically print documents without invoking the print dialog.
But then my employer also needed such a solution in order to automatically process incoming work orders and print their PDF attachments to a network printer in the production hall. And so I had to take a closer look at how to automatically print eMails and attachments in Apple Mail. After happily spending an entire morning in front of my office iMac, I am proud to share my little AppleScript snippet with you. May it also make your future workdays a little bit easier:
[center]pergamail - Automatically print incoming eMail messages and attachments in AppleMail (ca. 35.3 KB)
(v0.1b, pergamail is BETA software)[/center]
An eMail message printed out by pergamail might look like that:
Requirements
pergamail was tested under/with:
¢ Mac OS X 10.5.1
¢ Apple Mail 3.1
¢ Intel & PowerPC based Macs
Installation
To install pergamail 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:
All rule-matching eMails will then be processed by pergamail and - hopefully - printed out accordingly.
Configuration
pergamail offers some advanced options, which you can directly set in its source code. Just open the script in the Script Editor and have a look at the first lines. When making changes to pergamail, please always save it as a «Script bundle», as it also contains a little Python script, which translates byte sizes into a human readable form.
«defprintapp»
property defprintapp : "TextEdit"
-- default value: "TextEdit"
-- possible values: "lpr" or "TextEdit"
You can set defprintapp to print either via the shell using the «lpr»-command or via TextEdit, which can also automatically print documents using AppleScript without invoking the print dialog. If TextEdit was not running on your Mac at the time pergamail started its work, it will quit it afterwards.
«defprintername»
property defprintername : missing value
-- default value: missing value
-- possible values: any name of an available printer, e.g. "HP LaserJet 1300"
By default, pergamail is printing to the currently set printer. But if you always want to print to a specific printer, just provide the printer name here. After pergamail is finished, it will always set back your printer to the one used before.
«defprintatms»
property defprintatms : false
-- default value: false
-- possible values: false or true
If you want to try to also print out the attachments of the eMail messages, then set this value to true. But please note that pergamail can only print attachments, which can be printed out using the «lpr»-command (PS, PDF, RTF(D), TXT, HTML, most image formats natively supported by Mac OS X).
«defmsginfo»
property defmsginfo : "short"
-- default value: "short"
-- possible values: "short" or "long"
If you set this property to “long”, some more infos will show up in the mail header, e.g. message size.
Notes
Please note, that pergamail currently doesn’t support fancy HTML eMail very good. You won’t see any pictures or formatted text in the printouts from pergamail. Maybe this can be overcome in the future by parsing the raw source of the eMail messages with a Python/Ruby/Perl script, then extracting and saving the HTML part, and finally printing it with the «lpr»-command. Maybe.
Moreover I encountered some problems, when I tried to use pergamail with my Bonjour-shared HP network printer at home. But it runs just fine in the company. So if you encounter any problems, please remember that pergamail is currently just a little beta script. It’s not an expensive and mature application.
Important: If pergamail encounters an error, it creates a log file named «pergamail.log» on your desktop containing the last error message.
With a little bit of AppleScript knowledge (maybe even without) you can easily adjust the pergamail eMail output to your own requirements. Just look for the «msgtotxt» function.
Future
Currently pergamail creates text files from incoming eMail messages, but it would be much better to directly create HTML or PDF documents to get more control over the print layout. previmail alreday uses an advanced HTML template and Colendar shows hot to create a PDF on the fly, so I might just need to invest another weekend to write an improved version of pergamail
Last but not least, all parts of pergamail can be inspected with the Script Editor. I invite everybody to modify, enhance and misuse pergamail for their own purposes.
Happy eMail printing!
Important: Opening and saving the below script code in Script Editor won’t result in a usable AppleScript! That is because pergamail internally relies on a Python script, which is located inside its Script bundle. Therefor please download the complete script here.
-- created: 05.02.2008
-- Installed together with an AppleScript-based Apple Mail mail rule,
-- this script - hopefully - prints out all rule-matching eMails (and their
-- attachments according to the options manually set below)
-- The script was tested under/with:
-- ¢ Mac OS X 10.5.1
-- ¢ Apple Mail 3.1
-- ¢ Intel & PowerPC based Macs
property mytitle : "pergamail"
property myversion : "0.1b"
-- here you can set the application which is going to
-- print the eMail message: TextEdit or lpr
-- the default value is 'TextEdit'
-- why 'TextEdit'? because it can print without
-- invoking the print dialog
-- lpr is pure printing via the command line
-- possible values: "TextEdit" or "lpr"
property defprintapp : "TextEdit"
-- you can choose a default printer to be used, e.g.
-- "HP LaserJet 1300"
-- default value is missing value
property defprintername : missing value
-- do you also want to try to print the eMail attachments?
-- please note, that you can currently only print
-- attachments of the following types (due to the use of the 'lpr'-command):
-- PDF, RTF(D), TXT, HTML, most image formats natively
-- supported by Mac OS X
property defprintatms : false
-- 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
repeat with matchmsg in matchmsgs
my printmailmsg(matchmsg)
end repeat
end perform mail action with messages
end using terms from
-- deactivated debug routine
(*on run
tell application "Mail"
set msg to item 1 of (selection as list)
my printmailmsg(msg)
end tell
end run*)
-- main function controlling the script procedure
on printmailmsg(msg)
try
-- converting the Apple Mail message to a plain text report
--set msgtxt to "Hallo"
set msgtxt to my msgtotext(msg)
-- finding and getting an unused temp file
set tmpfilepath to my TmpFile's newpath()
-- saving the plain text report in the temp file as UTF-8
my writetofile(msgtxt, tmpfilepath)
-- printing the temp file
my printfile(tmpfilepath, defprintapp)
-- deleting the temp file
my TmpFile's remove()
-- printing the attached files if possible
if defprintatms is true then
my printatms(msg)
end if
-- THE END
on error errmsg number errnum
set logmsg to ((current date) as Unicode text) & ": " & errmsg & " (" & errnum & ")"
set logfile to ((path to desktop) as Unicode text) & "pergamail.log"
my writetofile(logmsg, logfile)
end try
end printmailmsg
-- I am printing the given file with the chosen application
on printfile(filepath, printapp)
-- if the user chose to use a default printer,
-- we must know the current printer, as we might have
-- to set it back later
if defprintername is not missing value then
set curprintername to my getcurprintername()
my setcurprinterbyname(defprintername)
end if
-- printing via the command line
if printapp is "lpr" then
-- silence is golden...
set cmd to "lpr " & quoted form of (POSIX path of filepath)
set cmd to cmd as «class utf8»
do shell script cmd
-- printing via TextEdit
else if printapp is "TextEdit" then
-- if TextEdit is currently not running, we
-- should quit it afterwards
if appisrunning("TextEdit") then
set tewasrunning to true
else
set tewasrunning to false
end if
-- printing the file without invoking the print dialog and
-- without bringing TextEdit to the foreground
tell application "TextEdit"
set newdoc to open (filepath as alias)
print newdoc without print dialog
close newdoc
end tell
-- cleaning up...like mother told us
if not tewasrunning then
tell application "TextEdit"
quit
end tell
end if
end if
-- setting back the default printer to its prior value
if defprintername is not missing value then
my setcurprinterbyname(curprintername)
end if
end printfile
-- I am printing the attachments of a mail message if possible
on printatms(msg)
-- saving all attachments in temporary files
set tmpfilepaths to {}
tell application "Mail"
set atms to mail attachments of msg
repeat with atm in atms
set atmname to name of atm
repeat
set rndnum to random number from 1000 to 99999
set tmpfolder to (path to temporary items folder from user domain) as Unicode text
set tmpfilepath to tmpfolder & "tmp_" & rndnum & "_" & atmname
try
set tmpfilepath to tmpfilepath as alias
on error
exit repeat
end try
end repeat
try
save atm in tmpfilepath
set tmpfilepaths to tmpfilepaths & tmpfilepath
end try
end repeat
end tell
-- printing the temporarily saved attachments and then deleting them afterwards
-- not all attachment types can be printed (e.g. MS Word/Exel, etc.)
repeat with tmpfilepath in tmpfilepaths
my printfile(tmpfilepath, "lpr")
tell application "Finder"
delete (tmpfilepath as alias)
end tell
end repeat
end printatms
-- I am returning the name of the current printer
on getcurprintername()
tell application "Printer Setup Utility"
return name of current printer
end tell
end getcurprintername
-- I am setting the current printer to the given printer name
on setcurprinterbyname(printername)
tell application "Printer Setup Utility"
set allprinters to every printer
set printernames to name of every printer
if printername is not in printernames then
return false
else
if name of current printer is not equal to printername then
set current printer to printer printername
end if
return true
end if
end tell
end setcurprinterbyname
-- I am converting an Apple Mail eMail message to a plain text report
on msgtotext(msg)
set newline to ASCII character 10
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 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
--set msgpath to my getmsgpath(msgid)
end tell
-- basic report:
set msgtext to "+ + + + + + + + + + + + + + + + + + + + + + + + + + + +" & newline & newline
set msgtext to msgtext & "SUBJECT: " & msgsubj & newline & "FROM: " & msgsender & newline & "DATE: " & msgrecvd & newline & "TO: " & msgtorec
-- further enhancing the report if information is available
-- >> CC recipients?
if msgccrec is not missing value then
set msgtext to msgtext & newline & "CC: " & msgccrec
end if
-- >> BCC recipients?
if msgbccrec is not missing value then
set msgtext to msgtext & newline & "BCC: " & msgbccrec
end if
-- >> long info?
if defmsginfo is "long" then
set msgtext to msgtext & newline & "SIZE: " & msgsize & newline & "SENT: " & msgsent & newline & "REPLY-TO: " & msgreplyto
end if
-- >> attachments?
if msgatms is not missing value then
set msgtext to msgtext & newline & newline & "##########" & newline & msgatms & newline & "##########"
end if
set msgtext to msgtext & newline & newline & "+ + + + + + + + + + + + + + + + + + + + + + + + + + + +" & newline & newline & msgcont
return msgtext
end msgtotext
-- I am returning the recipients of an eMail message as plain text list
on gettxtrecipients(rectype, msg)
-- I am returning the recipeints as a text list, e.g.:
-- "Martin Michel <martin@joyofscripting.com>, Steve Jobs <steve@mac.com>"
-- 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 & " <" & recaddress & ">"
on error
set textrecipients to textrecipients & "<" & recaddress & ">"
end try
else
try
set textrecipients to textrecipients & recname & " <" & recaddress & ">, "
on error
set textrecipients to textrecipients & "<" & recaddress & ">, "
end try
end if
end repeat
return textrecipients
end if
end tell
end gettxtrecipients
-- I am returning the eMail attachments as a plain text list
on gettxtatms(atms)
set textatms to ""
if atms is {} then
return missing value
else
set countatms to length of atms
if countatms is equal to 1 then
set textatms to ("1 Attachment:" & return)
else
set textatms to (countatms & " Attachments:" & return)
end if
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 & return
end if
end repeat
end tell
return textatms
end if
end gettxtatms
-- 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 returning the file pathof the eMail message based on its message ID
on getmsgpath(msgid)
set mailfolder to ((path to library folder from user domain) as Unicode text) & "Mail"
set cmd to "find " & quoted form of (POSIX path of mailfolder) & space & "-name" & space & msgid & ".emlx"
set cmd to cmd as «class utf8»
set shellresult to do shell script cmd
if shellresult is "" then
return missing value
else
return shellresult
end if
end getmsgpath
-- 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 as «class utf8») to openfile
close access openfile
return true
on error
try
close access openfile
end try
return false
end try
end writetofile
-- I am indicating if a given application is currently running
on appisrunning(appname)
tell application "System Events"
set processnames to name of processes
end tell
if appname is in processnames then
return true
else
return false
end if
end appisrunning
-- 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 & (rndnum as Unicode text) & ".tmp")
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
on remove()
try
set command to "rm " & quoted form of (POSIX path of (filepath as Unicode text))
do shell script command
end try
end remove
end script