Screw your contrast

September 18, 2004

Bash email script with attachment

I’ve been helping a friend with a website he’s been developing. I have a little more experience with *NIX than he does so I’ve been pitching in with help on cron and bash scripts and a few other linux commands.

He needed to dump the database to a CSV every day at a specific time and email the file, as an attachment, to a predetermined address. Further throwing a twist to the challenge, he couldn’t install software on the server, (since he doesn’t own it), thereby negating the use of mutt, which would have greatly simplified matters.

Bash to the rescue

So, I decided that it would be easiest if I could write a script that would generate a form email that I could attach the CSV and pipe to sendmail and run it from cron at the specified time every day. Sounds simple enough, right? Well, it appears that line spacing is very important when generating raw emails. That and a proper boundry string and I finally figured out how to write a script that will mail a form letter with an attachment using bash.

The script

As I mentioned earlier, this is a form mail. The variables can be changed to reflect your particular needs.

#!/bin/bash

###################################################
#  The following is all you should have to modify #
#  in order to get the script working for you.    #
#  This script presupposes that you have a        #
#  working sendmail on your system.  An alias to  #
#  postfix or msmtp should work though.           #
###################################################

from=your_email@ddress.org
subject="Cron-Bash attachment email script"
msgdate=`date +\"%a, %e %Y %T %z\"`  # Leave alone
boundary=GvXjxJ+pjyke8COw            # Leave alone
archdate=`date +%F`                  # Leave alone
attachment=/path/to/attachment.ext
archattachment="/path/to/attachment-${archdate}.ext"
emailtarget=recipient@ddress.org

###################################################
#  The text below Content-Disposition: inline can #
#  be modified to suit your needs.  It's          #
#  important that you escape all quotes in the    #
#  body of your message or the script will fail.  #
###################################################

daemail=$(cat <<!
Date: $msgdate
From: $from
To: $emailtarget
Subject: $subject
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$boundary\"
Content-Disposition: inline

--$boundary
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

This is the text that makes up the body of the email.
Anything you want to appear in the body of the message
should be put here.  Try to keep it generic since it's a
form email.  Something along the lines of,

\"The results of xyz script has been attached to this
email.\"

--$boundary
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=\"attachment.ext\"
!)

echo "$daemail" > msg.tmp
echo  >> msg.tmp
cat "$attachment" >> msg.tmp
echo  >> msg.tmp
echo "--$boundary--" >> msg.tmp
email=`cat msg.tmp`
echo "$email" | /path/to/sendmail -t
rm msg.tmp
mv $attachment $archattachment
touch $attachment

Download the tarball of this script.


Updated | March 9, 2007

The above script was intended to work with text attachments only, but I keep getting questions such as, "I'm using your script to send tarballs of blah, blah, blah...". This is why I don't claim to be a good writer, I don't clearly convey my thoughts as well as I think I do.

Okay, I've received some email and I've made a few corrections to the new script. The program mimetype was replace with file -i which does the exact same thing and reduces a dependancy that I hadn't even listed before.

The script still automagically handles whatever type of attachment you hand it, be it text/plain, or some kind of binary goodness, but it does so without having a bunch of perl libraries installed.

It has also been pointed out that the "begin-base64 666 filename.ext" line that uuencode prepends to the encoded binary output messes up the email for some (most) MTA's and, so, that's been fixed. Hopefully, this should all work as advertised now.

This script has had a few more eyes on it, still, if you try it and it doesn't work, email me and we can hammer out the bugs together.

This script has a few dependancies:

The -m switch to uuencode is to enable base64 encoding for a binary attachment. Base64 encoding is not available on earlier versions of uuencode or the BSD version IIRC.

New and improved™ script


#!/bin/bash

###################################################
#  The following is all you should have to modify #
#  in order to get the script working for you.    #
#  This script presupposes that you have a        #
#  working sendmail on your system.  An alias to  #
#  postfix or msmtp should work though.           #
###################################################

from=your@ddress
emailtarget=recipient@ddress
subject="Cron-Bash attachment email script"

# An alias to postfix or msmtp should work here
sendmail=/path/to/sendmail

# Need full path to attachment.  Replace path with $1
# and then call `scriptname /path/to/attachment` and
# it should work.
attachment_path=/path/to/attachment

# Backup emailed attachments to this directory in
# case of network error.
archive_path=/path/to/backup/directory

CONTENT=$(cat <<-ScrewEscapingQuotes
You should be able to type out whatever the hell you
want in here an not have to worry about escaping
stupid quotes.

Hey, I don't like escaping quotes anymore than you
do.
ScrewEscapingQuotes)

###################################################
#  You shouldn't have to modify anything beneath  #
#  this line.                                     #
###################################################

msgdate=`date +"%a, %e %Y %T %z"`
boundary=GvXjxJ+pjyke8COw
archdate=`date +%F`
attachment=`basename "$attachment_path"`
archattachment="${archdate}-${attachment}"
mimetype=`file -i $attachment_path | awk '{ print $2 }'`
daemail=$(cat <<!
Date: $msgdate
From: $from
To: $emailtarget
Subject: $subject
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=\"$boundary\"
Content-Disposition: inline

--$boundary
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

$CONTENT

--$boundary
Content-Type: $mimetype;name=\"$attachment\"
Content-Disposition: attachment;filename=\"$attachment\"
Content-Transfer-Encoding: base64

!)

echo "$daemail" > msg.tmp
echo  >> msg.tmp
if [ "$attachment" = "text/plain" ]; then
	cat "$attachment_path" >> msg.tmp
else
	uuencode -m $attachment_path $attachment | sed '1d' >> msg.tmp
fi
echo  >> msg.tmp
echo "--$boundary--" >> msg.tmp
email=`cat msg.tmp`
echo "$email" | $sendmail -t
rm msg.tmp
mv $attachment_path $archattachment
touch $attachment

Download the tarball of this script.