Let's say we're working on a project with our friend Bob, and we want to share some files with him, foo.c and readme.txt. But how...
As I stated in my earlier tutorial, the purpose of the MIME headers is to indicate type information about what you're sending. Unfortunately there's no I-Am-An-Attachment header, but there are a few headers we can use in combination to get the desired effect. Take a look:
To: "Bob" <bob@yahoo.com> From: "Me" <me@codecall.net> Subject: Source Code MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="FILEBOUNDARY"
Wait...what?! What happened to text/plain? Well, we need to tell the mail server that we're sending an email with more than one part, i.e. the message and some attachments. Because the server has no way of knowing how long each of the parts are, we pass the boundary argument. We will have to stick this in between every part of the message, on its own line, preceded by two dashes. The end of the message must have the boundary marker, but with two dashes at the end as well:
--BOUNDARY blah blah part one --BOUNDARY blah blah part two --BOUNDARY blah blah last part... --BOUNDARY--
Your mail client (i.e. Gmail, Yahoo, etc.) will a much longer and more random boundary marker, like --smtp.yahoo.com-dargueta-310820091952GMT-5--. Why? Well, imagine if one of the files I'm sending just happens to contain the text --BOUNDARY on a line by itself. Oops. The mail server is going to interpret that as the end of the file...and that'll screw everything else up. Better to have a ridiculous boundary that no one is going to read anyway than to have a simple one and run the risk of messing up the message.
Wait, we're not done yet...immediately after each boundary marker (except the last one indicating the end of the message, of course) we have to declare the type of the part we're sending. In this case, it'll be text/plain for each of our parts since we're sending just plain text files. We also need to add a header declaring each part as either an attachment or part of the message text that Bob is going to read, like so:
Message body:
--FILEBOUNDARY Content-Type: text/plain Content-Disposition: inline Hello, World!
Attachment:
--FILEBOUNDARY Content-Type: text/plain Content-Disposition: attachment; filename="whatever.txt" Some text file containing unimportant stuff here. . **...sendmail might choke on that...we'll have to make sure to pass -i to avoid this problem.
Notice that again, we have to leave a blank line to signal the end of the headers. Putting it all together, here's our final email:
To: "Bob" <bob@yahoo.com> From: "Me" <me@codecall.net> Subject: Source Code MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="FILEBOUNDARY" --FILEBOUNDARY Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Hello, World! --FILEBOUNDARY Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: attachment; filename="foo.c" #include <stdio.h> int main(void) { printf("Hello, World!\n"); return 0; } --FILEBOUNDARY Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: attachment; filename="readme.txt" This is a program that prints "Hello, World!" to the console and exits. --FILEBOUNDARY--
I slipped in something extra--did you notice the charset=iso-8859-1 parameter? It tells the email client what character set to use to render the text. If I were sending text in Russian, for example, I could put UTF-8, which is one of the most widely supported Unicode formats. It's always good practice to put this parameter in, otherwise the client will use the user's preferred setting - and that can cause problems. Say someone in China decides to send me a text file. They may write it with the Latin alphabet, but since their default encoding is UTF-8 and mine is ISO 8859-1, I'm going to see a bunch of garbled symbols. So always specify your character set.
Now, let's say we want to send Bob a different kind of file - a binary file. Say a .tar.gz. Clearly we're going to have problems here...binary doesn't transfer well because many systems truncate bytes to 7 bits. Why, I don't know. But - happily, we have a way around this, called base-64 encoding. By using only the characters 'a'-'z', 'A'-'Z', '0'-'9', '+', and '/' ('=' is used in special cases as well), you can encode anything. Think of it like hexadecimal, except base-64 and not base-16. Luckily, *NIX systems come with a utility to help us in our quest. First thing we need to do is encode our file:
diarguet@rtp-lds-035:~$ cat myarchive.tar.gz | uuencode -m /dev/stdout > myencodedarchive.base64
Ugly, I know, but for some reason I can't get it to work any other way. uuencode also has an annoying habit of sticking this begin base64 ... line at the beginning, so make sure to remove that first line before you put it in your email. If you open your file, you should get something frightening, like this:
R0lGODlhEgATAJEAAAAAAP///wCZAP///yH5BAEAAAMALAAAAAASABMAAAIo jI+pGyK8nINqUiTfbVnfvHEg1UmhdZRqaawu6XZVjKb0/CYxo8JOAQA7 ====
If I typed that right, it should be a little GIF image of a tree. Great! So now we've got our archive all nice and encoded...now what? Well, we need a MIME type for this. Er...well, we can look at the IANA list of all MIME types and find the appropriate type with some work, or we can look at this list that lists MIME types by file extension. Note that it's not complete and some of the extensions may not be listed. For those that aren't, you'll have to make the judgment yourself. In this case, since we're using a .tar.gz file, we want application/x-compressed. So, off we go:
--FILEBOUNDARY Content-Type: application/x-compressed; charset=iso-8859-1 Content-Disposition: attachment; filename="myarchive.tar.gz" Content-Transfer-Encoding: base64 jdaia89UJU4VEWQ+JAOISP90JCDmlagnkjznckjdzn and so on...
Note the new header: Content-Transfer-Encoding. We need to put this for anything that doesn't have a MIME type starting with text/, because that means it's probably binary, and there are several different ways of encoding binary data.
Putting it all together, we get:
To: "Bob" <bob@yahoo.com> From: "Me" <me@codecall.net> Subject: Archived data MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="FILEBOUNDARY" --FILEBOUNDARY Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Hello, World! --FILEBOUNDARY Content-Type: application/x-compressed; charset=iso-8859-1 Content-Disposition: attachment; filename="myarchive.tar.gz" Content-Transfer-Encoding: base64 jdaia89UJU4VEWQ+JAOISP90JCDmlagnkjznckjdzn... --FILEBOUNDARY--
There we go! Now you can send text emails with attachments to all your family and friends. I think this tutorial has gone long enough, so I'll get to HTML emails and embedding stuff in the next tutorial.
Missed part 1? Click here to go back.