Procmail in 5k

Got a unix account? Want to do mail filtering such as sorting, forwarding, etc as mail comes in? Then procmail may be just what you need.

Procmail comes with several programs. The most useful are:

procmail
This does the bulk of the processing
formail
Handy for post-processing and header manipulations

A good way to start off is to install a very small, very safe, .procmailrc first.

From a shell prompt, run these commands to make an initial procmailrc:

echo SHELL=`which sh` > ~/.procmailrc
echo DEFAULT=$MAIL >> ~/.procmailrc
echo LOGFILE=$HOME/.procmaillog" >> ~/.procmailrc
echo LOGABSTRACT=all >> ~/.procmailrc
chmod o+x $HOME

That sets various critical variables to reasonable values. DEFAULT is where to save mail unless otherwise instructed; LOGFILE will have records of all deliveries and any errors reported; SHELL is which program to use to run commands, sh-like shells work best. The chmod makes sure the mailserver will find your .forward.

Now with that setup, on to the .forward file. To use procmail you need to pipe your mail to the program. Here's a shell command to set up the .forward for you:

echo '"|IFS=" ";exec '`which procmail`" #$HOME" > ~/.forward
IFS is to safeguard against an identity stealing bug on some systems. $HOME makes this unique to you (work around a sendmail bug). Now send yourself a test message and you should get it and see the delivery logged in the ~/.procmaillog file.

On to the filtering!

Each filter ruleset is called a recipe. Recipes can forward mail or deliver mail (after either of these processing stops by default), filter mail, set variables, or gate access to other recipes. Each recipe begins with ":0" with nothing besides spaces before it. Then there can be some flags (see below). Then if delivering to a file there should be another ":" to use locking. If unsure, just put the second colon in. Here are the common flags, they can be combined in any order:

H
Check the headers during tests -- default unless B is used
B
Check the body during tests
D
Don't ignore capital/small differences during tests
h
Recipe sends the headers out -- default is headers and body unless using this or b
b
Recipe sends the body out
f
Recipe is a filter: read back in whatever was sent out
c
Copy this mail for processing in later recipes: use on forwarding and delivery recipes to not stop there
w
Wait for an exit code: recommended on a filter
E
An else-if clause to the previous recipe
e
An error handler for the previous recipe after, eg, a filter fails

On to the conditions!

Each condition line starts with any amount of spaces, followed by a *, then come the flags for that line, then scoring rules, then the actual condition. Condition flags and scoring rules are both optional. These are the two flags to know about:

$
Expand variables on this line
!
Negate the condition

A cheat sheet on scoring:

* N^M test
If M is 0, N will be added once if the test succeeds
If M is 1, N will be added each time the test succeeds
If M is some other value, see the formula in procmailsc. A recipe that scores 0 or less does not get run.

The tests on condition lines are (mostly) regular expressions which will be applied to the headers by default. After all the condition lines, comes the action line. Actions take the form:

| program
Pipe to (or filter with) program
! addresslist
Forward mail to addresslist
{ code }
Apply the procmail code: use for setting conditional variables and nested recipes
directory
Save mail in a uniquely named file in directory
directory/.
Similar to above, but the name will be a sequence number
directory/
Save mail in a maildir folder
filename
Append mail to file

Now some sample recipes:

:0:
* ! ^(To|CC):.*\<me@mysite
* ! ^From:.*\<(friend1@asite|friend2@somesite|me@mysite)
$HOME/junk.box

If it is not explicitly to me@mysite, or from a few trusted addresses, then save it to junk.box. This will catch mailing list mail, if you subscribe to any.

:0
* ^From:.*\<mysweety@heartland
! me@mywork
Forward mail from mysweety to me@mywork.
:0
* ^To:\/.*
{
TO=$MATCH
}
:0 ^CC:\/.*
{
TOCC=$TO,$MATCH
}
Grab and save the To: header, then the CC: header. (\/ will save the matched text in $MATCH).
:0hfw
* -10^0
*   1^1 TOCC ?? @
| procmail -A 'Junk: too many recipients'
Start the scoring at -10, then add 1 for each @ found in $TOCC. (The VARIABLE ?? bit at the start of the condition makes it apply to the variable.) If more than 10 add a new header on them label the mail as junk. Use such headers to test spam recipes before filtering to a file.