MH & nmh: Email for Users & Programmers

May, 2006

Alternatives to mhook Programs

Some systems with MH can't use the mhook utilities because the MTA doesn't support them, because the MTA runs on another computer, and so on. You may want your mail processed in batches, late at night -- instead of one by one, with .maildelivery.

Besides, the .maildelivery file and mhook programs can't do everything:

I hope that gives you an idea of what's possible. You can replace part or all of the mhook utilities with utilities that you develop or get from someone else. This chapter explains the basic concepts and shows a few examples. Another example is the autoinc script. For more about shell programming and using MH utilities like scan in your programs, see the Chapter Introduction to UNIX Programming with MH, and any good shell programming book (try the Reference List). With shell programming, as well as utilities like awk and perl, you can develop your own ways of handling mail automatically.

Replacing All of Your .maildelivery File

You may not need to use the .maildelivery file at all.

One of the most popular replacements for a .maildelivery file is procmail. This widely-available program by Stephen R. van den Berg is a very powerful and flexible way to filter incoming mail. The syntax is even worse than .maildelivery, but the end result is really worthwhile if you have the patience to learn. If you have nmh version 0.23 or above, look for the MAIL.FILTERING file at the top of the source tree; it has a brief introduction and suggested setup for using procmail with nmh. You'll find more information at, in Procmail Quick Start (or its mirror), on the newsgroup, and the FAQ.

Here's another possibility. Your system's MTA may be able to start a program of yours for each new message you get. I think it's safer (for beginners, at least) not to do this. If your handler fails, your mail could bounce. Instead, run your mail handler from the .maildelivery file, as the Section Running Your Own Mail Handler explains. But that's your choice, not mine!

How to Set Up

If your system uses the popular sendmail or smail MTAs, you can probably start your own mail-handling program by putting its pathname in your .forward file. For example, this line in my .forward file will start the program /u/jerry/bin/inmail:

    "| /u/jerry/bin/inmail"
If your system handles executable files that start with #! (see the Section How Does Your System Execute Files?), you can probably execute shell scripts that way by just giving the script's name. Otherwise, start scripts by giving the pathname to the interpreter -- for example:
    "| /bin/sh /u/jerry/bin/inmail"
The MTA will start the program and feed the incoming message to the program's standard input. When the program finishes and returns a zero exit status, the message will have been delivered.

Gotchas: UID, GID, Permissions...

If your mail handler is run directly by the system MTA, you can run into some tough problems making it work correctly. On our Sun with SunOS 4.1, for instance, the sendmail MTA will run a private mail handler in several ways:

The rules are different in some other versions of sendmail. So, running your handler this way may mean some careful programming. You might run your handler from .maildelivery (see the Section Running Your Own Mail Handler) -- this is a safer but maybe less efficient way to do it. To find out more about the environment set in your handler, start with a Bourne shell script like the one in the Example below, Mail handler for debugging. (You can also get the script from this book's online archive. It's in examples/mh/bin/inmail-show.) It stores the incoming message in one temporary file. It also opens a temporary log file. The log file stores the verbose output from the shell and the output of two commands that help you see the environment while the handler runs. Both files are created in /tmp; you can list them with ls -l to see what permissions and ownership your handler will give files it creates.

Example: Mail handler for debugging

    #! /bin/sh
    exec > /tmp/inmail$$.log 2>&1    # Send all output to log file
    set -xv     # Make shell show debugging output
    /bin/id     # On BSD systems, use "whoami" and "groups" instead
    /bin/env    # On BSD systems, use "printenv" instead
    /bin/cat > /tmp/inmail$$.stdin   # Grab incoming message
    exit 0      # Important to set a zero status if handler succeeds

The vacation Mail Handler

Many UNIX systems come with the Berkeley vacation program. It's designed to run from a .forward file, sending automatic replies when you're away from the office. When I run it, my .forward file looks like this:

    \jerry, "| /usr/ucb/vacation jerry"
If you just want to acknowledge incoming mail or send a standard reply, vacation can be a good way to do it. The details about the format of the reply, as well as who will get replies and how often, should be in your online vacation(1) manual page.
WARNING: Your version of vacation may require that you let it make a .forward file for you before it will enable itself (by creating its .vacation.dir and .vacation.pag database files).

This happened to me on Solaris: I ran vacation from the command line to enable the vacation system. It asked if I wanted to delete my existing .forward file (which was running slocal) and disable the vacation feature. I answered "no." Then, when I used vacation from my .maildelivery file (see below), it started a mail loop that filled my system mailbox. The fix was to:

  1. Quickly remove the .forward to stop the looping,
  2. Let vacation create a .forward file (and, more important, .vacation.dir and .vacation.pag).
  3. Edit the .forward file to run slocal again.

You may also be able to run vacation from your .maildelivery file. This lets you tailor when vacation will run -- for instance, to send vacation replies only to messages with the subject "lunch." The problem with this is that vacation expects the first line of each mail message to start with "From " and the sender's address -- but, typically, when slocal sees a message, its first line isn't "From ". The fix depends on your system. Here's how Bill Wohler fixed his problem: piping each incoming message through the sed stream editor. This changes the shielded ">From ". line to the "From " that vacation expects:

    # Uncomment when on vacation
    #to  wohler | R "sed -e '1s/>From /From /' | /usr/ucb/vacation wohler"
    #cc  wohler | R "sed -e '1s/>From /From /' | /usr/ucb/vacation wohler"
But that may not be enough; your MH setup may completely remove the incoming "From " line. In that case, the next example shows another way to build a new line. It uses the $(sender) variable to expand the user's address (this is the same address that slocal took from the original "From " line). It needs the version of the UNIX date command that has formatting escapes. The five date escapes %a %b %e %T %Y give the day of the week, the month, the day of the month, time, and four-digit year. If your date command doesn't have formatting, you can probably omit the argument starting with +. The cat command reads the message from the slocal pipe and passes it to the output of the subshell (and the pipe to vacation). The subshell (the parenthesis operators) and the semicolon (;) collect the output of echo and cat and pass them, in order, to the pipe to vacation.
    default - > R /var/mail/luis
    default - | ? "(echo \"From $(sender)  `date +'%a %b %e %T %Y'`\"; cat) | vacation luis"
If you're having trouble, add a pipe through the UNIX command tee -a the before vacation; you'll see the message that vacation is getting. Note that you may need to use absolute pathnames (like /usr/ucb/vacation) if executable commands aren't in the search path that slocal sets.

Running Your Own Mail Handler

Do you want to have all incoming messages processed by your own mail handler? It's probably safer to run your handler from a .maildelivery file entry than to have the MTA run your handler directly (see the Section Replacing All of Your .maildelivery File). That's because, if no entry in your .maildelivery file delivers a message, the system defaults will be used; you'll still get the message and it won't "bounce back" to the sender. Also, when your MTA delivers a message through .maildelivery, the environment doesn't have so many inconsistencies.

Setting Up

To feed all of your mail to your handler, put a entry in your .maildelivery file like one of the three below. Use the first entry if your handler can be executed directly. The second entry runs awk; the third runs a Bourne shell:

    *  -  ^  A  /u/jerry/bin/inmail
    *  -  ^  A  "/usr/bin/awk -f /u/jerry/bin/inmail"
    *  -  |  A  ". /u/jerry/bin/inmail"
Of course, you can use fields besides * to control what messages are passed to your handler -- and results besides A to control what other entries in .maildelivery are executed.

The exit status that your handler returns is important. If your handler returns a nonzero status, .maildelivery will act as if that the handler failed. Try to make your handler set a zero status when it succeeds. (The section Using Exit Status explains. The exit 0 command sets a zero status in Bourne shell scripts.)

Printing Incoming Mail

You can print incoming mail from .maildelivery. For instance, the following entry would print all messages sent to the company-standards address. As always, the R means that messages won't be marked "delivered" when they're printed:

    to   company-standards   ^   R   "/usr/ucb/lpr -Plaser"
The print job may come out with a banner page that says it was printed by daemon or some other user you wouldn't expect. (See the Section on Gotchas.)

Replacing rcvtty with Pop-Up Windows

If you use a window system like X, rcvtty will tell you about new mail in a window where you're logged in. That window might be buried behind other windows, iconified, or full of other text when important mail comes in. Will you see the notice? This script, rcvxterm, makes sure that you will. It's designed for the X Window System, but it should be easy to adapt for other window systems.

If you don't need a pop-up window, you might look at the script anyway. It uses a technique for running programs from .maildelivery that's good to know.

The script should probably be run only for important mail. It opens a red xterm (X terminal emulator) window in the top-right corner of an X display. The title bar will say (depending on your window manager):

    Important mail.  Press q to quit.
There'll be a copy of the mail message inside the window, shown by a pager program like less or pg. (Note that, to keep the window from closing too soon, the pager program can't exit until you select the window and type a command like q. The more pager quits at the end of the file, so it won't do.)

The script is simple on purpose: to make it easy to customize for your window system, and to make it easy to understand. The environment inside your .maildelivery file has almost no information about where you're logged on. So, I hard-coded most of the information into the script. If you need help understanding the script, see the Chapter Introduction to UNIX Programming with MH. To run it, put an entry like the one below in your .maildelivery file. (If your system can directly execute files that start with #!, you can omit the /bin/sh.)

    From  root  |  R  "/bin/cat >/tmp/m$$; /bin/sh /x/y/rcvxterm /tmp/m$$ &"
The script reads a file named on its command line. It could read its standard input, the message from the .maildelivery file. But actions in .maildelivery that don't finish after a reasonable amount of time are killed automatically. To be sure your window stays there, and to avoid blocking other actions in .maildelivery, run rcvxterm in the background as shown above. Use cat to copy the message into a temporary file. (Note that, during .maildelivery, your umask is set at 077. That keeps other users from reading the temporary file.)

The script is below. (You can also get the script from this book's online archive. It's at examples/mh/bin/rcvxterm.) If you haven't installed a shell script before, there's help in the Section Writing Shell Scripts for MH.

NOTE: If you leave this simple-minded demonstration script running while the display is unattended for long periods, it can overload your system with too many processes and/or open windows. You might hack the script to count the number of rcvxterm processes -- with ps and grep -c, for instance -- and quit without opening an xterm if there are too many running already.
    #! /bin/sh
    # $Id: rcvxterm,v 92/08/02 18:19:27 jerry book2 $
    ### rcvxterm - HACK script to notify you about new mail
    ### Usage in .maildelivery: "/bin/cat >/tmp/m$$; /x/y/rcvxterm /tmp/m$$ &"

    trap '/bin/rm -f $1' 0 1 2 15   # REMOVE TEMP FILE BEFORE EXITING

    /usr/bin/X11/xterm -display hostname:0.0 \
        -geometry 80x24-0+0 -bg red -fg white \
        -title "important mail.  Press q to quit" \
        -ut -e /usr/local/bin/less $1

Processing with at or cron or by Hand

Instead of running your handler separately each time a new message comes in, it can be better to run a separate handler periodically. On systems where you can't run a handler for each new message, this is your only choice!

A handler can read your system mailbox file directly, usually with inc. I think it's easier to do that, splitting your messages into a folder -- then have my handler run MH programs like scan and pick to handle the messages.

Most handlers are probably shell or perl scripts. A script can be run by hand when you type its name at a shell prompt, or your computer can run it automatically with at or cron. One automatic handler is the autoinc shell script.

After you write a script like autoinc, add an entry to your personal crontab file or start an at job to run your script whenever you want. It's probably a good idea to run it late at night, if you can. The load on the system might be less then. Also, and more important, there's less chance of the handler being executed while you're logged on and might be reading mail -- it could change your current folder and current message while you weren't expecting it. (You can avoid that by making the handler use a different MH profile, context, and sequences file. The rmmer script uses most of those tricks.) If you haven't used crontab or at before, there are examples. Also, be sure to check the exit status of commands like inc to see if something has gone wrong -- the Section Using Exit Status has tips.