Saving Mail message attachments


(Mark Alldritt) #1

Here is a snippet of code showing how to save email message attachments using the Mail application. The script processes all selected messages and saves each attachment to the Desktop:

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

--	Save attachments of selected Mail messages to the desktop

set desktopPath to (path to desktop)'s POSIX path

tell application "Mail"
	repeat with aMessage in (get selection)
		repeat with anAttachment in mail attachments of aMessage
			set attachmentName to name of anAttachment
			save anAttachment in POSIX file (desktopPath & attachmentName)
		end repeat
	end repeat
end tell


And if you want to return a value (e.g. a list of all the filePaths saved to)

(to enable notification, for example, or composition within some larger process)

then concatMap (combined with list-wrapping of returned filePaths), allows (by concatenation), for a pruned list – with no ‘empties’ where where selected messages turned out to have no attachments.

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

--    Save attachments of selected Mail messages to the desktop

set folderPath to (path to desktop)'s POSIX path

tell application "Mail"
    script attachmentsSaved
        on |λ|(mailItem)
            script fpSaved
                on |λ|(x)
                    set fp to folderPath & (name of x)
                    save x in POSIX file fp
                end |λ|
            end script
            concatMap(fpSaved, get mail attachments of mailItem)
        end |λ|
    end script
    -- Return value: file paths of any saved files
    my concatMap(attachmentsSaved, get selection)

    --> {"/Users/houthakker/Desktop/labour draft.docx", "/Users/houthakker/Desktop/Socialist_dress_outline.isf"}
end tell

-- GENERIC  ------------------------------------------------------------------

-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
    set lng to length of xs
    set acc to {}
    tell mReturn(f)
        repeat with i from 1 to lng
            set acc to acc & |λ|(item i of xs, i, xs)
        end repeat
    end tell
    return acc
end concatMap

-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
            property |λ| : f
        end script
    end if
end mReturn

(Vince Angeloni) #3

how is this better than using Mark’s script with this as the fist script line:

set myFilePathList to {}

and putting this in the deepest repeat loop for the attachment move:

set myFilePathList to myFilePathList & posix file of (desktopPath & attachmentName)



Whether it is ‘better’ would depend on what you want to optimise for, and what is familiar to you.

Pasting in a pre-cooked and reusable concatMap is just an alternative to hand-crafting your own repeat loops and associated variables each time.

(Boundary conditions and variable mutations in loops can tend to be the location of a fair proportion of bugs and glitches, and copy-pasting reusable generic loops does seem, empirically, to reduce the bug count a bit, as well as saving some typing).

Unfamiliarity is of course, also a speed bump, so mileage on the value of this approach is likely to be variable.

(Vince Angeloni) #5

Heh — yeah. I’d have to reread Matt Neuburg’s book just to understand what is going on in that script. Thanks for posting, though… gives me something to aspire to learn.


map or filter may be good places to start.

(Matt Neuburg does present a filter in his book, though his is perhaps a little more complex, and, being recursively implemented, runs out of stack space and returns an error with longer lists (c. 600)).