Composing and Sending MIME Messages

[previous] [next] [table of contents] [index]

MIME message format can be complex. Luckily, you don't need to enter all of the MIME stuff by hand. Instead, as you've probably read in earlier sections of this book, you enter special directives in your draft; the MH mhn command or the nmh mhbuild command checks the directives and converts them into a MIME-formatted message. The Section Sending MIME Mail introduces MIME message composition; Sections Building MIME Drafts and Recovering MIME Drafts cover the processing of MIME message drafts. That information isn't repeated here.

Example Drafts with Directives

Some people like examples, then explanation. This section is for you to "learn by doing." Other people like to read the rules before they try the examples. If you'd rather see the rules first, read the Section MIME Draft Directives and see the tables of directives in the Section Syntax of MIME Draft Directives.

You shouldn't use these examples exactly as they're shown. You'll need to substitute your own messages and your own files. If you can't do an example because your terminal doesn't support that type of content -- for instance, you're using a plain character terminal that can't show images -- try the example once, anyway, to see what happens. If you have access to several kinds of displays, try the customization steps in the Section MIME Configuration; these will make each display work the best it can.

Find a MIME-equipped friend to exchange messages with. You can have some fun by finding images and sounds from your local host (with the "fast find(1)" or locate(1) commands) or from anonymous FTP archives on the Internet (with archie or a Web search engine). Search for files whose names end with .gif and audio filenames ending in .au. There are also some interesting files on ftp://thumper.bellcore.com/pub/nsb/.

(If you always use an X Window System display, you might also want to try exmh. It handles MIME in a different and much more graphical way.)

Before we get into the examples below, here are rough steps to follow as you compose and encode the test messages.

  1. Use comp to compose a draft. Put directives in the message body. Remember that mhn or mhbuild will add all the MIME header fields; you don't need to do that.
  2. At the What now? prompt, type edit mhn. Or, if you use nmh, you can type mime instead of edit mhn. If you get errors, re-edit the draft (with edit vi or your favorite editor) to fix them.
  3. List the draft. If your lproc: MH profile entry has show, you'll see the message as the recipient would. In that case, you should also list the encoded draft with a pager so that you can see the header fields and other things done during MIME encoding -- try edit more or edit less.
  4. If you don't like the changes for MIME, or if you'd like to experiment, bring back the original draft and modify the directives. Then run edit mhn or mime again. The Section Recovering MIME Drafts explains how.
  5. Send the draft -- or type quit delete (in nmh, just delete is enough) to remove the draft and quit.
Now, on to the sample messages.

Text and a Picture

This is a typical MIME message. It has two parts: plain text and a non-ASCII image. If you found an image file in GIF format, you can send it in this message. Otherwise, pick some non-ASCII data; choose the correct content-type from the Section Some MIME Content Types and Subtypes.

If your terminal's character set isn't us-ascii, be sure to use some non-ASCII text (for instance, accented characters) in your message. Then look at the encoded message; it should have a quoted-printable encoding now. The Section Choosing MIME Encodings has more information about encoding non-ASCII text.

% comp
To: "V.I. Editor" <vie@a.b.com>
cc: me
Subject: something
-------
Dear someone,

Here's a picture of something.  Blah blah blah...
#image/gif [Picture of something] /u/joe/testfiles/something.gif

CTRL-D
-------
What now? edit mhn

What now? list  ..or..  edit more

    ...message appears...

What now? s  ..or..  q d
Notice that to send plain text without a Content-Description: field, you can just enter the text without a directive. The plain text should have become a text/plain body part. The image/gif part should come next, with a Content-Transfer-Encoding: of base64 and a Content-Description: "Picture of something."

Plain Text with a Content-description

You may want to describe your plain text body parts by adding a Content-Description: field. There are two ways to do that. I like to use the #< directive. But you can also type the Content-Description: field as the first line of the plain text part -- and then (important!) follow it by an empty line.

The message below has two text parts. The first is information; the second is a "signature." Most people put an end-of-message signature file in their home directories in a file named .signature (with a leading dot). mhn and mhbuild can include that signature for you as a separate body part. (The Sections Automatic Signature on End of Messages and Add Text to Drafts: mysend explain how to add a signature automatically.) If you have a long signature, putting it in a separate part is courteous. People who don't need to read it can just skip that part of the message. (On the other hand, if your message would have had only one part, adding the signature in a separate part makes the message multipart. That adds more complication for the person who's reading your message.)

Here's the sample body. (In examples from here on, I won't show the message header or the What now? prompts.)

#<text/plain [Some useless garbage]
Hi, whoever.  Blah blah blah...
etc. etc. etc.
#text/plain [signature] /home/joe/.signature
After you build the MIME draft, it should have two parts: text/plain with the Content-Description: "Some useless garbage," and text/plain with your signature and a description.

Enriched Text

A text/enriched body part lets you do simple formatting: changing fonts, text size, and layout. (A similar but obsolete type is text/richtext.) RFC 1896 is a complete specification of enriched text; Section RFCs and Internet Drafts explains how to get it. The most popular use of enriched text is probably for boldfacing and centering:

The example later in this section shows a sample enriched text message.

You may have an editor that supports enriched text. Enriched text is similar to SGML, HTML, and other markup languages, so you might be able to adapt an editor for one of those. But enriched text is really simple to enter, so I've never looked for a fancy editor. Plain old vi makes it pretty easy. I've written a set of two-character vi macros (key maps) that enter the tags. There are versions for both command mode and text-input mode. (Also see the example Emacs keybindings and macros for entering enriched text; they're documented in the mh-e section. If you don't use vi or Emacs, you can still use this idea.) For instance, when I put my cursor on a word in the command mode and type *b (asterisk, then a lowercase "b"), the macro surrounds the word with <bold> and </bold>. In text-input mode, if I want to enter a bold word, I type *b; the macro inserts the tags <bold></bold> and puts the cursor between them, ready to enter text. In both cases, when I've finished editing the bold text, I can type *e to go to the end of the bold area (the final > character).

Some of the macros are shown in the example below. The sequence ^[ stands for the ESC character, which you enter by typing CTRL-V first:

Example: vi Macros for entering enriched text

; *b macros: tag current word as bold, add bold tags then insert between:
map *b ea</bold>^[F<bi<bold>^[
map! *b <bold></bold>^[F<i
; *e macros: go to end of tagged area by searching for ">":
map *e f>
map! *e ^[f>a
   ...
You can get the whole batch of macros from the online archive in the download/split/mh/misc/exrc.enriched file; there's more informetion in the download/split/mh/misc/exrc.README file. Of course, you can edit them to work the way you want. For instance, you might want the command-mode *b macro to let you boldface a whole area of text instead of a single word. You could change the map *b to insert <bold> before the cursor and add a new *B to append </bold> after the cursor. If you often need to enter text that contains the literal characters *b without invoking the boldfacing macro, try naming your macro with an equal sign (=b) instead of an asterisk. Also, many versions of vi let you map the function keys (F1, F2, etc.) with mapping commands like map #1. If you have an enriched-text editor, mhn and mhbuild can be configured to run it automatically as you compose a message. You'll need the mhn-compose- profile entry.

On to the example message:

#<text/enriched [Line-filling tests]
This is the first line of the message.  It is <bold>very long</bold>.  When this line is encoded, it should be
broken (with an = at the end of the line) into quoted-printable text.

The empty line above will cause a line break.  So, the word
<italic>The</italic> should be
at the left-hand margin.


The two empty lines above
make this into a new
paragraph.
Because the default body part is text/plain, you have to use a #<text/enriched directive before the enriched text part. If you enter any lines that are too long (more than 76 characters), mhn or mhbuild will automatically use quoted-printable encoding for your text -- even if your text has only ASCII characters.

Remember that the recipient's enriched text viewer will reformat your message to fit the screen. Line breaks are handled this way:

Here's an idea of what the sample enriched text message will look like on the recipient's screen:

This is the first line of the message. It is very long. When this line is encoded, it should be broken (with an = at the end of the line) into quoted-printable text.
The empty line above will cause a line break. So, the word The should be at the left-hand margin.

The two empty lines above make this into a new paragraph.

Enriched text is designed to be fairly readable on non-MIME mail programs. When you look at your encoded draft (which isn't shown here), compare the encoded quoted-printable message to the formatted version.

Data from a Program, Character Sets

mhn and mhbuild can run a program to provide content for a message part. The Section about mhn-compose- and mhbuild-compose- entries tells how to specify the default program to be run for each content-type. You can also give a program name at the end of a directive:

#type/subtype [description] |some-program
This will run some-program instead of any default composition program that might be defined for that content-type. The standard output of the program will be used as the content. But if the program returns a non-zero exit status, mhn or mhbuild will abort; it won't encode the draft. To fool mhn/mhbuild into using the output of the program anyway, use a subshell with an exit 0 command on the end. Using the subshell also lets you combine several commands, and anything they write to their standard output, into one content. For example, to change to another directory (not the one where you're composing the draft) and mail the output of an rcsdiff command (which returns an exit status of 1 if there were differences):
#text/plain [diff] |(cd /u/elsa/project/summaries; rcsdiff -r8.7 refguide; \
    exit 0)
You can continue a directive to another line by typing a backslash (\) at the end of the first line.

By the way, to simply mail the output of a program -- with no other message -- using comp and a directive is probably more work than you need to do. The mhmail and viamail programs are probably what you want.

For this example, let's mail the output of a simple shell script. This script, named makechars, is useful for people like me who live in an "ASCII culture." It outputs a string of 26 eight-bit characters. If your keyboard isn't configured for a non-ASCII character set, you can run makechars to get some non-ASCII text.

To make makechars, put the following three lines in an executable file named makechars. (If you've never made a shell script before, the Section Writing Shell Scripts for MH explains how.)

#!/bin/sh
# Output characters with octal values 300 to 331
echo abcdefghijklmnopqrstuvwxyz | tr '[a-z]' '[\300-\331]'
If your terminal is configured for a character set like ISO-8859-1, here's what should happen when you run the script:
% makechars
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ
When you want to send non-ASCII text in a MIME message, you must tell mhn or mhbuild to use the non-ASCII character set. To do that, store the character set name in the MM_CHARSET environment variable. Use one of these two commands:

$ MM_CHARSET=iso-8859-1; export MM_CHARSET   sh-type shells
% setenv MM_CHARSET iso-8859-1     csh-type shells

If you always use that character set, you should make sure that environment variable is always set -- by your shell's startup file, for instance. (nmh users: there's more about MM_CHARSET in your mh_profile(5) manual page.)

It's time to make the draft. As you saw in the sample directive above, put the program name where a filename would go and start the program name with a vertical bar (|):

#text/plain [output of makechars] |makechars
(If the makechars program isn't in your shell's search path, you may need to use its absolute pathname, like |/home/andy/makechars.)

When you run a program to compose a content, it's often an interactive program that does something like recording your voice message. So mhn or mhbuild prints a message to tell you that it's starting the program:

What now? e mhn
composing content text/plain from command
        makechars
Because makechars isn't interactive, you should get another What now? prompt right away. View the encoded draft on your screen by "editing" the file with cat(1):
What now? e cat
To: jerry
Subject: testing makechars
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-8859-1"
Content-ID: <288.782317596.1@ora.com>
Content-Description: output of makechars
Content-Transfer-Encoding: quoted-printable

=C0=C1=C2=C3=C4=C5=C6=C7=C8=C9=CA=CB=CC=CD=CE=CF=D0=D1=D2=D3=D4=D5=D6=D7=D8=
=D9
There are a few things to notice in that encoded message. The character set parameter charset="iso-8859-1" has been added to the Content-Type: field. The Content-Transfer-Encoding: field shows that mhn or mhbuild has used quoted-printable encoding. You can see that in the body: each non-ASCII character is encoded as its hexadecimal value with = before. The encoded output of makechars is too long for one line, so mhn/mhbuild has split the line and added an = at the end of the first part.

If your lproc is set to run show, you should see the decoded message on your screen:

What now? list
To:      jerry
Subject: testing makechars

MIME-Version: 1.0
Content-Description: output of makechars

part       text/plain                  27 output of makechars                 
Press <return> to show content...
ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ

External Parts

Including non-text parts in your messages can make them big. Messages can easily get too big to send; systems will truncate or reject messages that are too long. The current MIME standard doesn't include any kind of compression because of the uncertain legal status of popular compression methods.

One way MIME works around the problem of large messages is by splitting a message into pieces and reassembling it automatically. That's called partial messages. But partial messages don't solve another problem: Some people may not want their mailboxes filled by huge messages. MIME has a nice answer for that problem: external parts. A MIME mail reader can ask the recipient whether to fetch a copy of some data by anonymous FTP, from an email file server, or from a local file. In other words, the mail message doesn't contain the data; it contains instructions for getting the data.

NOTE: Not everyone can use anonymous FTP: many email users aren't on the Internet or are behind a firewall. There are workarounds for MH users (the mhn-access-ftp: or nmh-access-ftp: entry). Some MIME MUAs can't handle external body parts, though. The recipient may be able view the encoded body part and get the data manually. Still, before you send a message with external body parts, you may want to ask the recipient.

The next example message has two external parts. It's a multipart/alternative message. One part refers to a file by anonymous FTP; the other refers to a local file. Remember that the order of parts in a multipart/alternative message is important: the last part is "best." In this message, I've decided that getting the contents of a local file is better than getting the file by anonymous FTP. So, I've put the local-file part at the end of the message body. If the recipient can't get the file by the second method (a local file), their MIME-capable mail program will use the first method (anonymous FTP).

This message has dummy directives to get and display the mhn(1) manual page. You should edit the directives. If you can put files in an anonymous FTP area, or if your site has an anonymous FTP area with some interesting files to get, use those parameters in the first external body part below. If you can't put files in an anonymous FTP area, send a message that recovers files from a public FTP area like ftp.uu.net or ftp.ics.uci.edu. Modify the second part to point to the formatted version, if any, of the mhn manual page on your system.

#<text/plain [instructions]
Please read the O'Reilly catalog.  It's in the parts below.  Thanks.
#begin alternative
#@text/plain [O'Reilly catalog by FTP] \
        access-type=anon-ftp; \
        permission=read; \
        size=180312; \
        directory="/pub"; \
        name="book.catalog"
#@text/plain [O'Reilly catalog or something else from local file] \
        access-type=local-file; \
        size=nnnn; \
        name="/directory/filename"; \
        permission=read
#end
Be sure to give a [description] in your external-part directives; mhn and mhbuild require it. If you don't want to add a description, use a pair of brackets with no text between them: []. For a complete explanation of the parameters for each external body part (which takes something like ten pages!), get a copy of RFC 2045.

MIME Draft Directives

This section is a (slightly) more formal description of the draft files that mhn and mhbuild read and encode into MIME messages.

A draft message body can have two types of lines:

Now let's take a closer look at the five types of MIME directives. This is a summary of what the directives do. The Section Syntax of MIME Draft Directives explains the syntax.
  1. The first kind of type directive, which starts with #, names the type and subtype of the content. It reads the content from a local file or from the output of a program.

    If a filename isn't given, mhn and mhbuild search the profiles for a composition string (see the Section What Profile Entries Are There?) that tell how to compose the content. For example, if you specify #audio/basic without giving an audio filename, mhn/mhbuild wants to run a program that captures sound.

  2. The second kind of type directive, which starts with #<, also names the type and subtype of the content. It takes the content from the following lines in the draft.
  3. External-type directives, which start with #@, name the type and subtype of the content. They create external body parts -- for example, parts available by anonymous FTP. External-type directives need plenty of extra parameters -- like access-type=anon-ftp.
  4. The message directive, #forw, creates a message/rfc822 or a multipart/digest content. It forwards existing email message(s).
  5. The multipart directives #begin and #end, which always come in pairs, create a multipart content. Other directives between #begin and #end create the parts. The multipart directives can be nested to create subparts.
Most directives take some optional syntax parts that create additional header fields. Not all directives accept all of these parts; see the syntax descriptions in the Section Syntax of MIME Draft Directives for details. In general, if you use any of these optional parts, you have to enter them in the following order:
  1. MIME parameters for the Content-Type: header field start with a semicolon (;). For example, if you're sending a tar file compressed with gzip, this directive:
    #application/octet-stream; type=tar; x-conversions=gzip /u/joe/fs.tar.gz
    
    would make the following field in the message:
    Content-Type: application/octet-stream; type="tar"; x-conversions="gzip"
    
  2. Parentheses, ( ), add a comment to the end of the Content-type: header field. For example, if my terminal's character set is us-ascii, the following directive:
    #text/plain (using British spelling) [Sales report] /usr/reports/sales.UK
    
    would create these two fields in the message:
    Content-Type: text/plain; charset="us-ascii" (using British spelling)
    Content-Description: Sales report
    
  3. Angle brackets, < >, give the value of a Content-ID: header field. This should be in legal RFC 822 format. It should also (theoretically, at least) be unique: no other message part sent before or after this, from anyone in the world, should have the same Content-ID:.

    If you omit <content-id>, mhn or mhbuild will choose a value with the date, time, and your local hostname. If you use empty angle brackets, <>, the message part won't have a Content-ID: field.

  4. Square brackets, [ ], enclose a description for the Content-Description: header field. If you don't use [ ], you won't get a Content-Description: field. (Exception: in a message/partial, mhn and mhbuild will add the description "Part m of n".)
If all this syntax stuff is starting to feel a little overwhelming, don't worry! It'll come naturally after you use it to send some MIME messages. Learning MIME is a little like learning a new computer language: there are some hurdles to get over before you can work naturally.

Partial Messages

Messages that are too long will be rejected or truncated as they're sent across a network. (Often, messages that stay on a particular host can be longer, but they still may have length limits.) The send -split option creates MIME partial messages as your draft is sent. Your draft will be split into parts that are about 50,000 ASCII characters long.

The mhn -store and mhstore commands shown in the Section Partial Messages can put the pieces together automatically. Or, if your message is just plain text without multipart contents, the recipient can probably put the pieces together by hand, without a MIME-capable mail reader -- although it may be inconvenient or impossible for users with menu-driven MUAs.

After you type -split, enter the number of seconds that send should pause before it sends the next part. For example, send -split 10 will give your mail transfer agent 10 seconds to process a part before the next one is sent. The right number to use depends a lot on your particular situation; if you're curious, talk to your system postmaster. (Note: -split 0 doesn't work. You have to use 1 or more.) To prevent splitting (for instance, if you've put something like send: -split 5 in your MH profile), use -split -1 at the What now? prompt.

To experiment, send a long file to yourself. If your system has /usr/dict/words, the dictionary file, it's a good choice. Mine has about 200k characters, which send splits into five messages. Be sure that your file isn't much longer than mine -- unless you want a lot of partial messages! Here's how to send the file with send -split:

% comp
To: jerry
cc:
Subject: Testing send -split
-------
#text/plain [words] /usr/dict/words
#text/plain [signature] /home/ehuser/.signature

CTRL-D
--------
What now? e mhn      (on nmh, just mime)

What now? s -split 1
Sending as 5 Partial Messages
pausing 1 seconds before sending part 2...
pausing 1 seconds before sending part 3...
pausing 1 seconds before sending part 4...
The first and second partial messages you sent will look like this:
% show -noshowproc 131 | more
(Message inbox:131)
From: Jerry Peek <jpeek@jpeek.com>
To: jerry
Subject: Testing send -split
MIME-Version: 1.0
Content-Type: message/partial; id="<6374.782021163@ora.com>"; number=1; total=5
Content-Description: part 1 of 5
Date: Wed, 12 Oct 1994 21:06:04 -0700
Message-ID: <6375.782021164@ora.com>

Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
Content-ID: <6370.782021158.0@ora.com>
Message-ID: <6374.782021163@ora.com>

------- =_aaaaaaaaaa0
Content-Type: text/plain; charset="us-ascii"
Content-ID: <6370.782021158.1@ora.com>
Content-Description: words

10th
1st
2nd
...omitted...
crease
create
creating
% show -noshow 132 | head -15
(Message inbox:132)
From: Jerry Peek <jpeek@jpeek.com>
To: jerry
Subject: Testing send -split
MIME-Version: 1.0
Content-Type: message/partial; id="<6374.782021163@ora.com>"; number=2; total=5
Content-Description: part 2 of 5
Date: Wed, 12 Oct 1994 21:06:23 -0700
Message-ID: <6382.782021183@ora.com>

creature
creche
credent
credential
...omitted...
The last message (in this example, the fifth message) has the closing part boundary.

It's a good idea to use the mhn -check or mhbuild -check switch when you compose a split message. See the Section Adding an Integrity Check.

Adding an Integrity Check

When a MIME message is encoded, mhn and mhbuild can add a Content-MD5: header field with a message checksum. The recipient can compare the message checksum to the message body. If the checksum doesn't match, the message body has probably been changed. The checksum method takes into account some of the common ways that gateways munge messages: it ignores trailing whitespace on lines, for example.

To add a checksum, use the -check switch with mhn. That is:

What now? edit mhn -check         (in MH)

What now? mime -check             (in nmh)
RFC 1864 documents the Content-MD5: field.

Choosing MIME Encodings

MIME encoding is designed to get the data safely through almost every known mail transport system and gateway. MIME encoding is also designed to make messages as easy for humans to read as possible. The Content-Transfer-Encoding: field tells the recipient how a message was encoded -- and how to decode it. The Section MIME Encoding has more information.

mhn doesn't let you choose the encoding it uses. It checks the message text and content type and then picks an appropriate encoding. (If you're using nmh, the mhbuild -ebcdicsafe switch lets you tweak the "quoted-printable" transfer encoding. Some common punctuation characters are encoded to get the message through mail gateways that use EBCDIC character encoding.) After you have experience with MIME, you might not always agree with the mhn or mhbuild encoding.

For example, I needed to mail a troff source file. A lot of the lines in troff source start with a dot (.) -- and leading dots cause some mailers to truncate files. But mhn chose plain 7bit encoding, which copies the text into the draft as-is. I wanted base64 encoding. So, after mhn encoded my message (edit mhn), I edited the draft (with edit vi, though any plain-text editor would do). I changed the Content-Transfer-Encoding: field value to base64. I deleted the 7bit-encoded body part -- and replaced it with the output from the mimencode(1) utility, being careful to keep the blank lines in the message body as they were. This is the vi command to do it:

:r !mimencode /my/troff/file/pathname
mimencode is part of the Metamail package; if you don't have it, see the Section Metamail.

The next release of MH will probably encode text messages (except text/plain) with leading dots as quoted-printable. The dots will be encoded as =2E.

NOTE: There are a few cases where a particular encoding is required, and there are other cases where the encoding is recommended. Before you change the default encoding, read RFC 2045 (The Section RFCs and Internet Drafts expains where to get it).

Here are the steps that the MH 6.8.3 version of mhn uses as it picks an encoding. (Thanks to Marshall T. Rose for this info.)
  1. If the content is multipart or message, 7bit is used.
  2. If the content is text,
  3. If the content is application/postscript,
  4. application content with only 7-bit values gets 7bit encoding.
  5. Everything else is encoded base64.
There seems to be at least one exception to the list above. (Thanks to Kimmo Suominen for pointing this out.) When you use mhn -check, text/plain content is encoded as quoted-printable. To find what actually happens, read the mhn source code; the filename in the MH source tree is uip/mhn.c.

nmh users: I'll bet the list above will change. Check the "Transfer Encodings" section of the mhbuild(1) manual page for the latest info.

[Table of Contents] [Index] [Previous: Working with Draft Messages] [Next: The comp Command]


Revised by Jerry Peek. Last change $Date: 1999/10/10 05:14:05 $

This file is from the third edition of the book MH & xmh: Email for Users & Programmers, ISBN 1-56592-093-7, by Jerry Peek. Copyright © 1991, 1992, 1995 by O'Reilly & Associates, Inc. This file is freely available; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. For more information, see the file copying.htm.

Suggestions are welcome: Jerry Peek <jpeek@jpeek.com>