NAME

proctut1 - Anatomy of a Procmail Recipe, Part I

SYNOPSIS

This tutorial will discuss the three core components of a procmail recipe and how they are used.

DESCRIPTION

As we discussed in Procmail Basics, procmail uses recipes to scan and decide what to do with mail. We looked at a simple recipe:

    :0:
    * ^From:.*joe@schmoe\.org
    joe

We mentioned that any mail passing through this recipe will be scanned for the email address 'joe@schmoe.org' in the 'From:' header (i.e., the message will be from joe@schmoe.org). Any mail matching this criterion will be delivered to a file called joe.

What's this first line ':0:' business?

To be brief, all procmail recipes have three components: flags, conditions, and an action. To be precise, only flags and actions are necessary (conditions are optional). The procmailrc(5) manpage states:

    A line starting with ':' marks the beginning of a recipe. It has
    the following format:

        :0 [flags] [ : [locallockfile] ]
        <zero or more conditions (one per line)>
        <exactly one action line>

But we prefer brevity over rigorous technical details[1]. (That's a little humor, folks.) We will be as accurate as we can for an introductory tutorial, but we may omit certain information until later tutorials. We will attempt to note those absences when they appear and flesh them out in later tutorials.

Flags

You've seen this in all procmail recipes:

    :0

What does it mean? Simply put, the procmail program needs to look for something in your recipe file to distinguish between recipes. :0 is the official start of a procmail recipe[2].

All of your recipes should begin with (at least) :0.

Header and Body

After :0 it gets tricky. Here is where you decide, in general, what part(s) of the mail will be scanned[3]. For example, procmail distinguishes between the email header and the email body. Knowing this distinction is important.

The header is everything from the very first line beginning with 'From ' (without a colon) down to the first empty line. Here is a very simple email message:

    From scott@perlcode.org  Fri Aug 22 22:41:40 2003
    Received: from localhost (localhost)
            by perlcode.org (8.12.9/8.12.9) id h7N4emRF047843;
            Fri, 22 Aug 2003 22:40:48 -0600 (MDT)
    Date: Fri, 22 Aug 2003 22:40:47 -0600
    From: Scott Wiersdorf <scott@perlcode.org>
    To: Scott Wiersdorf <scott@perlcode.org>
    Subject: test

    Hi!
    -- 
    Scott Wiersdorf

The header consists of the top 'From '[4] line[5] all the way down to the line beginning 'Subject:' The body of the email message is the line beginning 'Hi!' down to the signature 'Scott Wiersdorf' (yes, your author is rather vain).

More about flags

By default, procmail scans only the headers of the email message. This means that if you want your recipe to scan the body of the message, you'll need to use the B flag:

    :0B:
    * Hi!
    /var/mail/scott

You may wish to put spaces after :0, like this:

    :0 B:
    * Hi!
    /var/mail/scott

That's a little easier to read. Now we're scanning the body of the message (not the header). What if we want to scan both the body and the header?

    :0 HB:
    * Hi!
    /var/mail/scott

Now any messages that have the word 'Hi!' in either the headers (for example, the Subject line) or the body of the message will match this recipe.

There are many other flags that we will discuss in other tutorials. See procmailrc(5) for details.

Conditions

Now that we've covered what the flags do and how to use them, we need to move on to the meat of a procmail recipe. Procmail conditions are where you do all of your fancy footwork to make your recipes match just the messages you want (not too much, not too little).

Condition lines are optional, as we have previously seen[1]. A recipe that has no condition lines will match all email messages (you can think of having no conditions as being "unconditional" and the action of the recipe will always trigger[6]).

Most useful recipes have conditions, however, and all conditions begin with an asterisk: *. Here is an example of a single condition:

    * ^Subject:

In the context of a recipe, it might appear like this:

    :0
    * ^Subject: delete me
    /dev/null

If a recipe has multiple condition lines, all of the conditions must be true for the recipe to match. If any one of the conditions is not true, procmail skips the remaining conditions and moves to the next recipe. For example:

    :0
    * ^From: Unwanted Spammer
    * ^Subject: delete me
    /dev/null

If we receive an email with the phrase "delete me" as the subject, the first recipe above would dump the email message to /dev/null (deleting it forever).

The second recipe, however, also requires that the email message be sent from "Unwanted Spammer". If the message does not meet both of these requirements, the recipe will not match and procmail will move on to the next recipe (if there is one--if not, the mail will be delivered to its default mailbox as discussed in Procmail Basics).

More about conditions in procmailrc(5) and in later tutorials.

Action

Every procmail recipe has exactly one action line[7]. Most often, the action is the name of a file to deliver the mail to if all the conditions are met:

    :0:
    * ^From: Joe Schmoe
    $HOME/Mail/joe

The first line after the conditions (remember, all conditions start with a '*') is the action line. This action line will deliver mail from Joe Schmoe to the Mail/joe mailbox in my (the recipient's) home directory. We've seen other examples where mail was delivered to /dev/null, the bottomless pit of Unix.

There are a few other things you can do with an action line. For example, you can redirect an incoming email to someone else by inserting an exclamation point (!) at the start of the action line:

    :0
    ! bob@perlcode.org

The exclamation point tells procmail that we are going to be forwarding this email message to the email address specified next. Any email I get will be sent along to Bob instead. You can get this same effect with sendmail's virtusertable feature. However, one thing you can't get with the virtusertable feature is scanning:

    :0
    * ^Subject: Send this to Bob
    ! bob@perlcode.org

Now I only forward selected messages to Bob. That's harder to do with just sendmail.

Many more examples of action lines, including blocks, pipes, and filters can be found in procmailrc(5) and procmailex(5) and later tutorials.

SUMMARY

A procmail recipe is comprised of three basic components: a flags line (which always begin with :0), zero or more condition lines (which always begin with *), and an action line.

NOTES

Note 1

Because conditions are optional (zero or more), a minimal recipe could be:

    :0
    /dev/null
which will dump all email into /dev/null. You could also have:

    :0:
    /var/mail/foo
which will dump all email into /var/mail/foo. Notice the trailing colon on the flag line. This tells procmail that it should lock /var/mail/foo> so that other email messages that may be coming in don't try to write to the file at the same time (causing mailbox corruption; imagine one message interleaving with another message!).

/dev/null is a device and does not need a lock, since it can accept concurrent writes without any corruption at all. That's a little more humor, folks.

Note 2

Historically, the zero could be any digit, but since 1993 or so, the digit is almost always zero. The digit once referred to the number of conditions in the recipe, but the syntax has since solidified and obsoleted the necessity of counting conditions. Thank goodness!

Note 3

There are actually built-in procmail variables that you can use in the conditions themselves to scan different parts of the email (e.g., body and header). These are introduced in procmailrc(5) and in later tutorials.

Note 4

Notice there are two lines that begin with 'From'; the first one is inserted by your MTA (e.g., sendmail, qmail, postfix, etc.) to help it distinguish between messages (i.e., 'From ' is the official start of a new message). It contains what is called the envelope sender. This is the email address that sendmail received when the sender's email client spoke SMTP to the receiving server (well, that's an oversimplification, but it's generally true).

The second 'From' header has a colon in it; this is part of the email message that the sender actually inserted. Rather, the sender's email client (e.g., Eudora, pine, mutt, Netscape Mail, etc.) inserted it for the sender, but the sender specified them as part of their email client settings.

Note 5

'From ' is the de-facto way to refer to the MTA-added mbox format line. It is often different than From:, which the sender's email client sets. 'From ' is set by the receiving server from the envelope sender. This is getting way too technical, but suffice it to say that these headers are two different things; the 'From ' header is how nearly all mail readers separate one email message from another in mbox format. Lines in email messages that begin with 'From ' are often commented with '>' by the MTA so they don't cause problems.

Note 6

We recall from Procmail Basics that after the first matching and delivering recipe is found, procmail immediately stops processing this email message. So if you have two recipes:

    :0:
    /var/mail/joe

    :0:
    /var/mail/bob
only the first one will ever get triggered because it matches all emails unconditionally and procmail stops processing.

Note 7

Actually, it can also be a block, which is one or more recipes. A block begins and ends with curly braces ( '{ }' ) and may span multiple lines. For example, to avoid scanning the body (which can be very inefficient, depending on what you're looking for and how large the email message is) of an email message unless you absolutely have to, you can look for tell-tale headers first:

    :0
    * ^Subject: scan the body
    {
        :0 B
        * delete this message!
        /dev/null
    }

PREVIOUS

Procmail Basics

NEXT

Simple Regular Expressions, Part I

SEE ALSO

procmail(1), procmailrc(5), procmailex(5)

AUTHOR

Scott Wiersdorf <scott@perlcode.org>

COPYRIGHT

Copyright (c) 2003 Scott Wiersdorf. All rights reserved.

REVISION

$Id: proctut1.pod,v 1.7 2003/09/17 13:16:08 deep Exp $