Sending Email with Attachments from the Command Line

Posted on January 19, 2012

I have lots of awesome CLI based reporting tools. One was so awesome that other people in the company wanted to get it on a regular basis but they preferred to see it as CSV so it could be manipulated in Numbers or Excel. Modifying my report to output CSV was easy, I just added a conditional that replace my pretty column formated printf() with an ugly comma separated printf(). Sending CSV in email is easy, just pump it into ”sendmail -t”.

I quickly realized that using sendmail “as usual” sucked, because the CSV was in the body of the message, not an attachment. The solution was to send a Multi-Part MIME message. Doing so is easier than you think.

Lets look at a template example, piece by piece:

From: $FROM
To: $TO 
Date: $DATE
Subject: $SUBJECT
Mime-Version: 1.0 
Content-Type: Multipart/Mixed; boundary="ATTACHMENT-BOUNDRY"
Return-Receipt-To: $FROM

Some body stuff here, this is your message

Notice above that From, To, Date, is all pretty standard stuff. What is special is that we specify the MIME Version (1.0) and then set the content-type to “multipart/mixed”. Following that is a boundary string. A boundary string is an arbitrary string that represents the different parts of your message. In our case, it will separate the body from the attachments, but it can also be used for providing both HTML and Plain Text versions of a message in a single mail.

–ATTACHMENT-BOUNDRY
Content-Disposition: attachment;
filename=”$FILENAME1″
Content-type: text/plain;
charset=US-ASCII;
name=”$FILENAME1″
Content-Transfer-Encoding: quoted-printable

$ATTECHMENT_DATA1

The next section of of our message is noted by the boundary string prefixed by two dashes (–). Note that they are before but not after the boundary string! Next is the metadata about this portion of the message, namely the Content-type, encoding, and disposition.

It is important to note that Mail.app (OS X) is more strict about attachments than Thunderbird or Gmail. If you do not include a content-disposition it will register the section as just another part of the body. Mail.app requires that you be very careful about syntax, whereas Thunderbird and Gmail have a “I know what you meant” attitude.

--ATTACHMENT-BOUNDRY
Content-Disposition: attachment; 
        filename="$FILENAME2"
Content-type: text/plain; 
        charset=US-ASCII;
        name="$FILENAME2"
Content-Transfer-Encoding: quoted-printable

$ATTECHMENT_DATA2

--ATTACHMENT-BOUNDRY--

Here we have a second attachment. We could add as many as we wish, but notice that it ends with our boundary string again but now its surrounded by dashes front and back. This signifies the end our parts.

Thats really about it, pump all this into “sendmail -t” (ie: cat mymail.txt | sendmail -t, or equivalent) and away your mail goes.

One word about attachment type. Above the content type of the attachments was “quoted-printable”. That or 8bit are fine for normal text such as CSV, but if you wish to send binary data you will want to base64 encode it (see BASE64(1) for syntax) and set the content-type as “base64”.