MH & nmh: Email for Users & Programmers

May, 2006

The .maildelivery File in Detail

Before we get started, this is a good place (as good as any...) to mention an nmh feature. slocal will add a Delivery-Date: header field as it delivers each message -- no matter how it's delivered. MH only adds them to messages delivered to a file. Delivery-Date: can be handy with scan, pick, and other commands that let you act on any header field. For instance, you could write a scan format file to display the date and time a message was sent as well as the delivery date and time.

Each entry of .maildelivery has five arguments (except with the undocumented slocal select feature). There's either a comma or one or more spaces between each argument. If an argument has space or a comma in it, put double quotes (") around the argument. To include a literal double quote, type \" (put a backslash first).

This chapter uses eight normal words precisely, with an exact meaning. Because those words are used so often, I decided not to italicize them anywhere except where they're defined. The words are: argument, field, pattern, action, result, string, succeed, and delivered. The definitions are below.

The five arguments on a .maildelivery entry, explained in the following five sections, are:

field pattern action result string

There are a few other things to mention first:

NOTE: Many pathnames in these examples start with /x/y. That pathname is system-dependent; it's typically the MH library directory. If you can't find rcvstore and other commands in this chapter, ask your system administrator.

Now, let's look at the five arguments in each entry.

First .maildelivery Argument: Field

The first argument, field, refers to a header field in the message. In line 2 of the Example Simple .maildelivery file, for instance, the field argument from will match the From: field in a message header. You can specify any field that might be in a message header, including ones you make up arbitrarily (like X-auto-m-p: in the Section Handing Periodic Mail).

You can't always do what you need to by matching header fields, though. Maybe you get mail from several addresses or aliases, and you want to match any of those messages with one entry. Or, you might want to match all messages. So, the field can also have one of these four special values:

Matches the address that was used to cause delivery to the recipient. It's typically your username (mine is jerry). This is explained in the Section System Aliases, the to and addr Fields.
Matches the out-of-band sender information, the envelope sender. This is an address, supplied by the system's mail transfer agent. You'll usually see it as the first line of a message file starting with From somewhere, or stored in a Return-Path: header field.

The source is handy for matching mail from mailing lists. This doesn't work for all mailing lists, but it works for many. I'll use the mh-users list as an example. Message headers in mh-users have header fields like these:

      From  Mon Jan 09 23:43:16 1995
      Date: Mon, 09 Jan 1995 23:43:16 GMT
      From: Some Person <>
      Subject: Some random subject
You can't count on finding the address mh-users in any particular field (though it's usually somewhere on To:, Cc:, or Sender:). But you can bet that the out-of-band sender information, shown in the first line above, will have the address -- because that's the distribution point for the mailing list. So, an entry in .maildelivery that begins with the following two arguments will catch mail from the mh-users list:
      source  ...
Matches any message that hasn't been delivered by previous entries. For instance, look back at the Example Simple .maildelivery file: If either line 2 or line 4 delivers the message, line 6 won't. Otherwise, the message will always go into my mailbox.
An asterisk as the first argument matches every message, whether it's been delivered by other .maildelivery entries or not.

If the first argument has an * (asterisk), the action will always happen when the result (in the fourth argument) is A or R. A result of N or ? can cause the entry to be skipped when those conditions are met. The Section Fourth .maildelivery Argument: Result explains the result argument.

You can do more-sophisticated matching with a program like rcvsearch.

Second .maildelivery Argument: Pattern

The second argument, pattern, is the expression you want to match in the field selected by the field argument. For instance, line 4 of the Example Simple .maildelivery file searched for the expression "vax digest", in any combination of uppercase and lowercase, anywhere in the message Subject: field.

If the first argument is default or *, this second argument should be a dash (-).

The pattern argument is not a regular expression like the UNIX grep command uses; it's just a sequence of characters to match literally (such as the UNIX command fgrep). You can do regular expression matching with the rcvsearch program.

You can take advantage of the "substring matching" in the second argument. You might be looking for mail from root on any computer. Using root as a second argument will match mail from root with or without a hostname. You can also match mail from all users at a certain hostname (say, anyone at

The downside is that you can match some messages you weren't planning on. For instance, a search for mail from root might catch mail from Joe Grootenheimer.

One helpful trick takes advantage of the order that your .maildelivery file is read: from first entry to last. If you're trying to match more than one similar name, like joebob and joe, put the longest name first. The first entry that matches marks the message "delivered," so the entries below it won't deliver the message too:

    from  joebob  destroy  A  -
    from  joeb    qpipe    A  "/x/y/rcvstore +read_now"
    from  joe     qpipe    A  "/x/y/rcvstore +read_later"
Depending how your mail comes in, you may be able to add @ or ! to mark the start or end of the address. For example, to match mail from joe on a particular host and joebob@anyhost, use entries like the ones below. The joebob@ will never match mail from joe on any host:
    from   >  ?  somefile
    from  joebob@            >  ?  otherfile
Of course, that first entry would also match, but it's a start.

Whenever I use a destroy action (see the Section Third .maildelivery Argument: Action), I'm always very careful about what I match. The part of that Section about destroy shows a safer way with a temporary folder.

Third .maildelivery Argument: Action

The third argument is the action: how to deliver the message. There are four different actions. Each action can be written in two ways, as a word or as a symbol. (MH 6.8 added a new mbox action, a version of the file action. It can't be written as a symbol.) The actions are:

file or > (right angle bracket) Action

The file or > action appends the message to a file. (That's usually a file on the mail server computer; it may not be the same computer where you do most of your work. You might write the message into a filesystem that's shared between the two computers.) Put the filename in the fifth argument. Without a pathname, the file will be in your home directory. Typical mailbox names are /usr/mail/yourname or /usr/spool/mail/yourname.

The message is appended in "mailbox format." It's separated from the previous message in the file by either:

mbox and mmdf Actions

The MH 6.8 version of slocal added a new mbox action. It works like the file or > action except that it always uses the MMDF-style separators. There's no symbol (such as >) for mbox; you have to spell it out.

NOTE: Under nmh, mbox appends messages in mailbox format instead of MMDF format! nmh has an mmdf action that appends messages in MMDF format.

folder or + (plus) Action (nmh only)

This action puts a message into a folder by piping the message to rcvstore. It's only available in nmh; to do this in MH, use one of the pipe actions below.

qpipe or ^ (caret) Action

This starts a program (rcvstore, other mhook programs, or almost any UNIX program). The program reads the incoming message from its standard input. If the program succeeds and returns a zero exit status, then the action has succeeded. For example, to tell you when new mail comes in, set .maildelivery to pipe the message to rcvtty, like this:

    *   -   ^   R   /x/y/rcvtty
Here's the difference between the qpipe or ^ action and the pipe or | action: using the pipe or | action doesn't execute a program directly -- it starts a Bourne shell; the shell runs the program.

Are you using a simple command string, one that has no special characters like a semicolon (;) or ampersand (&) for a Bourne shell to interpret? Then it's more efficient to use qpipe or ^ instead of pipe or |. That efficiency can be helpful on busy mail server computers.

There's no command-search path set, so be sure to give the pathname for any command you list. For instance, this command probably won't work:

    *   -   ^   R   rcvtty
There are special variables that you can use in the fifth argument (the string) with the qpipe or ^ action. The easiest place to find typical settings of these variables is to run slocal -debug and look for the lines starting with vars[n]:, as shown in the Example Sample slocal -debug -verbose output. The variables are listed in the Table below.

Table: Variables Set for pipe and qpipe

$(sender) (typical value: xandra@x.y)
The "return address" for the message. This is usually supplied by the system mail transfer agent. Can be set as third argument on the slocal command line. The address where you'll want to send replies, instead, is probably $(reply-to).
$(address) (typical value: jerry)
The address that was used to deliver the message to you. That's usually your username. Can be set as the first argument on the slocal command line.
$(size) (typical value: 12345)
The size of the message, in bytes.
$(reply-to) (typical value: "J. Doe" <jd@x.y>)
The address where you should send replies. If the incoming message has a Reply-To: field, that address is used. Otherwise, this variable will contain the From: field.
Miscellaneous out-of-band information. What you get depends on your system and the situation. Can be set as the second argument on the slocal command line.
For example, to make rcvtty print the message Mail: nnn bytes each time new mail comes in, use:
    *,-,^,R,"/x/y/rcvtty -format \"Mail: $(size) bytes\""
The message size is filled in before rcvtty is invoked. The escaped double quotes (\") group the words between them into a single argument. With the quotes, rcvtty prints a message like Mail: 1232 bytes on the terminal. Without them, it would print just Mail: and drop the rest -- that's because there has to be just a single argument after the -format option.

Single quotes (') won't perform quoting inside double quotes. They will work inside double quotes with the pipe or | action, though.

Also watch out for variables like $(reply-to) that might have quotes buried in their values -- they can cause real trouble here. There's more about quoting in the pipe section below.

pipe or | (vertical bar) Action

The pipe or | action starts a Bourne shell to execute the command(s) in the fifth argument, string. The message is piped as standard input to the shell (and the command it runs). If you're running just one command, you may be able to save some execution time by using the qpipe or ^ action (explained above). But if you really need a shell, use pipe or |.

For example, I wanted to add a timestamp to a log file each time a message came in. I used the following entry in my .maildelivery file:

    From,uucp,|,A,"/bin/date >> uucp.msglog; /x/y/rcvstore +uucp_logs"
What is that fifth argument? The argument has two commands. The first command, /bin/date, appends a line with the current date and time (when a message is delivered) to the file uucp.msglog in my home directory. There's a shell semicolon (;) operator to separate the commands. The first command doesn't read its standard input, but the second command does -- so rcvstore reads the message that is piped to the shell. You can chain a series of commands with semicolons, as long as only one command reads the message from its standard input. (If more than one command needs to read the message, use a temporary file as shown in the Section Replacing rcvtty with Pop-Up Windows.)

The Table Variables Set for pipe and qpipe lists special variables that are set for the qpipe or ^ action. Those variables are also set for pipe and |. Here's an example. To send an automatic reply when I get mail from a user (sent to my system's gripe alias), I can add the following long entry to my .maildelivery file. This has two commands, separated by a semicolon:

    To,gripe,|,A,"/bin/echo \"Thanks for your $(size)-character message.  I'll handle it ASAP.  --Jerry\" | /bin/mail '$(reply-to)'; /x/y/rcvpack gripes"
This chapter shows lots of ways to use the pipe or | action; browse through the examples below.

destroy Action

(Before you read this section, take a look back at the definitions of succeeds and delivered at the start of the Section The .maildelivery File in Detail.)

The destroy action does nothing with the message, and always succeeds. This action only makes sense with the result argument A that marks the message as "delivered." (See the Section Fourth .maildelivery Argument: Result.) That's the whole point of destroy: to mark the message as delivered without actually delivering it to a file or program.

If this is the only entry in your .maildelivery file that matches the message, the text of the message will go nowhere. But if another entry has an * (asterisk) field, the message can still be given to a command or stored in a file.

Instead of using destroy, you might use rcvstore or rcvpack to write the message to a temporary folder or file instead. Clean out the temporary storage every so often -- use scan or msh to be sure no messages were "destroyed" that shouldn't have been. The Section Experimenting? Make Backups! has more about making backup copies of mail.

Want an example? Here's a short .maildelivery file with an example of destroy:

    From     uucp  destroy  A  -
    default  -     >        ?  /usr/spool/mail/jerry
    *        -     ^        R  /x/y/rcvtty
If the message is from uucp, the first entry matches it and "delivers" the message (to nowhere). The result argument, A, is important here: it means that if the destroy action succeeds (as it always does), the message is marked "delivered." The first entry works with the second entry and its ? result argument. The second entry only writes to my system mailbox file if the message has not been delivered yet. Any message from uucp will have been delivered (to nowhere) already, so it won't go into my system mailbox.

The third entry has an * (asterisk) field, which matches all messages. The R result means that rcvtty always runs to tell me about the message, whether the message has been delivered or not. So, even though messages from uucp have been "destroyed" and aren't delivered to my system mailbox, rcvtty will notify me about them anyway.

Fourth .maildelivery Argument: Result

The fourth argument in a .maildelivery entry is a single-character result. (As before, the definitions at the start of the Section The .maildelivery File in Detail are important.) These results are:

A Result

This result argument is probably the most common. If the field and pattern are matched, an A performs the action. If the action succeeds, the message is marked "delivered."

You'll usually use A actions toward the start of your .maildelivery file to match certain messages. Toward the bottom, you'll use ? to match messages that A didn't match (and "deliver").

R Result

Like the A result, an R will perform the action if the field and pattern on an entry match a message. But R never marks a message as "delivered."

If any entry above an R result marks the message delivered, using R can't undo that delivery. In fact, nothing can undo a "delivered" mark. The R is probably most useful at the start of .maildelivery for actions you want to run on some or all messages -- and not mark the message delivered, so that other entries can act on it.

? Result

The ? (question mark) will perform an action only if the message hasn't been delivered by some previous entry in .maildelivery. If the ? delivers a message, the message is marked "delivered."

Use ? in places where you've got a series of entries that might deliver a message -- you want the entry with the ? to deliver only if entries above didn't deliver.

N Result

The N result was added to slocal in MH 6.7.2. The N result is a little harder to explain. According to the MH 6.8.3 slocal manual page, using an N result performs the action only if both of these things are true:

If this action (with an N result) succeeds, then the message is marked delivered.

That's not quite the way N works for me. I looked at the source code (the usr_delivery() function in the file uip/slocal.c) and came up with this more accurate (I hope!) description:

The N result performs the action only if:

Of course, I could have missed something. (The C code is fairly complex. It's a collection of C switch structures that fall through from case to case, several flags that test and store information about previous actions and delivery, continue commands that suddenly skip to the next .maildelivery entry, and so on. I've tried to summarize what the code does; see the Section slocal Documentation vs. Real Life.) Also, that description could change in other versions of MH. You may want to be careful if you use the N result. Test your .maildelivery file to be sure it does what you expect.

Fifth .maildelivery Argument: String

The fifth argument, string, is a filename (for the file or > action) or a command line (for the qpipe or ^ and pipe or | actions). As for other arguments, if the filename or command line have any spaces or commas (,), surround the argument with double quotes (").

Because there are example strings all through this chapter, I won't give another example here.

Undocumented Arguments 6-8: select

The slocal command with MH 6.6 through 6.8.3 (and possibly in earlier versions) has an undocumented feature that lets you control actions by the time of day and whether you're logged in. (Because they're undocumented, don't depend on this feature in all MH versions.) The three undocumented arguments, which must be used together, are:

    select starttime endtime
Type the word select literally. starttime and endtime are two times in the format hh:mm, where hh is hours on a 24-hour clock (0 to 23) and mm is minutes.

If you use these optional arguments, the action will not happen if:

So, for example:
    # if I'm not logged on and it's not between 8 AM and 5 PM,
    # send my mail to dan:
    *  -  |  R  "/x/y/rcvdist dan" select 8:00 17:00
To ignore the time restrictions, set both times to something like 0:00 (midnight); then the action will happen whenever you aren't logged in.

Regular Expression Matching with rcvsearch

By default, the .maildelivery file matches fixed strings in the message header. For example, to match messages with bin in the To: field, you could use:

    to,bin,|,R,"/x/y/rcvdist operator"
But this matches any address that contains the address bin -- including, for example, robin. The rcvsearch script handles this problem and more. It does full regular expression matching on a message header or body. You run rcvsearch from a pipe or | action in the .maildelivery file. rcvsearch compares the entire message to an egrep regular expression. If egrep returns a zero exit status (meaning the search succeeded), rcvsearch runs a command like rcvdist. (rcvsearch also returns a zero status so that the pipe or | action will succeed.) Here's the previous example rewritten to match any From: field containing bin (and nothing else) or bin@:
    *,-,|,R,"/x/y/rcvsearch '^From: bin(@?|$)' /x/y/rcvdist operator"
Here's a way to refile messages whose bodies contain "make money" or "sex" into the spam-check folder:
    *,-,|,A,"/x/y/rcvsearch 'make money|sex' /x/y/rcvstore +spam-check"
Once you see rcvsearch, you'll see that the idea is easy to adapt to do other things. For instance, you could rewrite the program in Perl to get more searching power -- or write a small program that does a specific test. Just remember that the program will be run for every incoming message; if you make it complex, it can slow down your incoming mail processing. The Section Explanation of rcvsearch has details.