PROCMAILEX(5)

๐Ÿ“ฆ
version 3.24 ยท 4 versions
, in centos-stream 10, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking

๐Ÿฆ‹ PROCMAILEX(5) โ€” procmailex, aka the rcfile example glow-up

File Formats Manual โœจ welcome to the part where we look at actual procmail rcfile recipes, bestie


๐ŸŽ€ NAME

procmailex โ€” procmail rcfile examples ๐Ÿ’–


๐Ÿ“ SYNOPSIS

$HOME/.procmailrc examples

๐ŸŒธ DESCRIPTION

okay so first things first: if you wanna know how the rcfile format actually works, go read procmailrc(5). That's the lore.

The whole weighted scoring thing? That's got its own dedicated deep-dive in the procmailsc(5) man page. Go get it.

This page is literally just a little gallery of example recipes ๐Ÿ–ผ๏ธ. And if you want to see what a complete rcfile looks like, check the NOTES section in procmail(1), or peek at the example rcfiles chillin' in /usr/share/doc/procmail/examples. iykyk ๐Ÿ’…


๐Ÿ‘‘ EXAMPLES

๐Ÿ  Example 1: the scuba list sorter

Sort out all the mail coming from the scuba-dive mailing list into the mailfolder scubafile (and yes, it uses the locallockfile scubafile.lock).

:0:
* ^TOscuba
scubafile

๐Ÿ’Œ Example 2: forward Peter's compiler mail to William

Forward all mail from peter about compilers to william โ€” and keep a copy of it right here in petcompil.

:0
* ^From.*peter
* ^Subject:.*compilers
{
   :0 c
   ! william@somewhere.edu

   :0
   petcompil
}

โœจ Example 3: same vibe, different shape

An equivalent solution that accomplishes the same thing:

:0 c
* ^From.*peter
* ^Subject:.*compilers
! william@somewhere.edu

   :0 A
   petcompil

๐ŸŒ Example 4: same thing but slightly slower

An equivalent, but slightly slower solution that accomplishes the same:

:0 c
* ^From.*peter
* ^Subject:.*compilers
! william@somewhere.edu

:0
* ^From.*peter
* ^Subject:.*compilers
petcompil

๐Ÿ›Ÿ Example 5: the safety net (for when you're new and experimenting)

If you're fairly new to procmail and plan to experiment a little bit, it often helps to have a safety net of some sort. Inserting the following two recipes above all other recipes will make sure that of all arriving mail, always the last 32 messages will be preserved. In order for it to work as intended, you have to create a directory named backup in $MAILDIR prior to inserting these two recipes.

:0 c
backup

:0 ic
| cd backup && rm -f dummy `ls -t msg.* | sed -e 1,32d`

๐Ÿฉน Example 6: fixing those janky From lines

If your system doesn't generate โ€” or generates incorrect โ€” leading From lines on every mail, you can fix this by calling up procmail with the -f- option. To fix the same problem by different means, you could have inserted the following recipe above all other recipes in your rcfile. They will filter the header of any mail through formail which will strip any leading From , and automatically regenerates it subsequently.

:0 fhw
| formail -I "From " -a "From "

๐Ÿ“š Example 7: hoarding headers (respectfully)

Add the headers of all messages that didn't come from the postmaster to your private header collection (for statistics or mail debugging); and use the lockfile headc.lock. In order to make sure the lockfile is not removed until the pipe has finished, you have to specify option w; otherwise the lockfile would be removed as soon as the pipe has accepted the mail.

:0 hwc:
* !^FROM_MAILER
| uncompress headc.Z; cat >>headc; compress headc

๐Ÿ—œ๏ธ Example 8: gzip era

Or, if you would use the more efficient gzip instead of compress:

:0 hwc:
* !^FROM_MAILER
| gzip >>headc.gz

๐Ÿ  Example 9: short mail goes home

Forward all mails shorter than 1000 bytes to my home address (no lockfile needed on this recipe).

:0
* < 1000
! myname@home

๐Ÿ„ Example 10: breaking up digests

Split up incoming digests from the surfing mailing list into their individual messages, and store them into surfing, using surfing.lock as the locallockfile.

:0:
* ^Subject:.*surfing.*Digest
| formail +1 -ds >>surfing

๐Ÿ“ฎ Example 11: catching the daemons

Store everything coming from the postmaster or mailer-daemon (like bounced mail) into the file postm, using postm.lock as the locallockfile.

:0:
* ^FROM_MAILER
postm

๐Ÿค– Example 12: a simple autoreply

A simple autoreply recipe. It makes sure that neither mail from any daemon (like bouncing mail or mail from mailing-lists), nor autoreplies coming from yourself will be autoreplied to. If this precaution would not be taken, disaster could result (ringing mail ๐Ÿ˜ญ). In order for this recipe to autoreply to all the incoming mail, you should of course insert it before all other recipes in your rcfile. However, it is advisable to put it after any recipes that process the mails from subscribed mailinglists; it generally is not a good idea to generate autoreplies to mailinglists (yes, the !^FROM_DAEMON regexp should already catch those, but if the mailinglist doesn't follow accepted conventions, this might not be enough).

:0 h c
* !^FROM_DAEMON
* !^X-Loop: your@own.mail.address
| (formail -r -I"Precedence: junk" \
    -A"X-Loop: your@own.mail.address" ; \
   echo "Mail received.") | $SENDMAIL -t

๐ŸŒด Example 13: the vacation(1)-core recipe

A more complicated autoreply recipe that implements the functional equivalent of the well known vacation(1) program. This recipe is based on the same principles as the last one (prevent ringing mail). In addition to that however, it maintains a vacation database by extracting the name of the sender and inserting it in the vacation.cache file if the name was new (the vacation.cache file is maintained by formail which will make sure that it always contains the most recent names, the size of the file is limited to a maximum of approximately 8192 bytes). If the name was new, an autoreply will be sent.

As you can see, the following recipe has comments between the conditions. This is allowed. Do not put comments on the same line as a condition though.

SHELL=/bin/sh    # only needed for older versions of procmail

:0 Whc: vacation.lock
 # Perform a quick check to see if the mail was addressed to us
* $^To:.*\<$\LOGNAME\>
 # Don't reply to daemons and mailinglists
* !^FROM_DAEMON
 # Mail loops are evil
* !^X-Loop: your@own.mail.address
| formail -rD 8192 vacation.cache

  :0 ehc         # if the name was not in the cache
  | (formail -rI"Precedence: junk" \
       -A"X-Loop: your@own.mail.address" ; \
     echo "I received your mail,"; \
     echo "but I won't be back until Monday."; \
     echo "-- "; cat $HOME/.signature \
    ) | $SENDMAIL -oi -t

๐Ÿ“ Example 14: TeX mail, unique filenames

Store all messages concerning TeX in separate, unique filenames, in a directory named texmail (this directory has to exist); there is no need to use lockfiles in this case, so we won't.

:0
* (^TO|^Subject:.*)TeX[^t]
texmail

๐Ÿ—‚๏ธ Example 15: same but MH-style

The same as above, except now we store the mails in numbered files (MH mail folder).

:0
* (^TO|^Subject:.*)TeX[^t]
texmail/.

๐Ÿงš Example 16: one mail, many folders

Or you could file the mail in several directory folders at the same time. The following recipe will deliver the mail to two MH-folders and one directory folder. It is actually only one file with two extra hardlinks.

:0
* (^TO|^Subject:.*)TeX[^t]
texmail/. wordprocessing dtp/.

๐Ÿ“… Example 17: folders that change every month

Store all the messages about meetings in a folder that is in a directory that changes every month. E.g. if it were January 1994, the folder would have the name 94-01/meeting and the locallockfile would be 94-01/meeting.lock.

:0:
* meeting
`date +%y-%m`/meeting

๐Ÿ› ๏ธ Example 18: auto-making the month folder

The same as above, but, if the 94-01 directory wouldn't have existed, it is created automatically:

MONTHFOLDER=`date +%y-%m`

:0 Wic
* ? test ! -d $MONTHFOLDER
| mkdir $MONTHFOLDER

:0:
* meeting
${MONTHFOLDER}/meeting

๐Ÿช„ Example 19: same, slightly different means

The same as above, but now by slightly different means:

MONTHFOLDER=`date +%y-%m`
DUMMY=`test -d $MONTHFOLDER || mkdir $MONTHFOLDER`

:0:
* meeting
${MONTHFOLDER}/meeting

๐Ÿ‘ฏ Example 20: killing duplicates

If you are subscribed to several mailinglists and people cross-post to some of them, you usually receive several duplicate mails (one from every list). The following simple recipe eliminates duplicate mails. It tells formail to keep an 8KB cache file in which it will store the Message-IDs of the most recent mails you received. Since Message-IDs are guaranteed to be unique for every new mail, they are ideally suited to weed out duplicate mails. Simply put the following recipe at the top of your rcfile, and no duplicate mail will get past it.

:0 Wh: msgid.lock
| formail -D 8192 msgid.cache
โš ๏ธ Beware if you have delivery problems in recipes below this one and procmail tries to requeue the mail, then on the next queue run, this mail will be considered a duplicate and will be thrown away. ๐Ÿ˜ญ

For those not quite so confident in their own scripting capabilities, you can use the following recipe instead. It puts duplicates in a separate folder instead of throwing them away. It is up to you to periodically empty the folder of course.

:0 Whc: msgid.lock
| formail -D 8192 msgid.cache

:0 a:
duplicates

๐Ÿ“ฌ Example 21: MH folders and the unseen sequence saga

Procmail can deliver to MH folders directly, but, it does not update the unseen sequences the real MH manages. If you want procmail to update those as well, use a recipe like the following which will file everything that contains the word spam in the body of the mail into an MH folder called spamfold. Note the local lockfile, which is needed because MH programs do not lock the sequences file. Asynchronous invocations of MH programs that change the sequences file may therefore corrupt it or silently lose changes. Unfortunately, the lockfile doesn't completely solve the problem as rcvstore could be invoked while show or mark or some other MH program is running. This problem is expected to be fixed in some future version of MH, but until then, you'll have to balance the risk of lost or corrupt sequences against the benefits of the unseen sequence.

:0 :spamfold/$LOCKEXT
* B ?? spam
| rcvstore +spamfold

๐Ÿงธ Example 22: emacs folders need emacs-compatible locks

When delivering to emacs folders (i.e., mailfolders managed by any emacs mail package, e.g., RMAIL or VM) directly, you should use emacs-compatible lockfiles. The emacs mailers are a bit braindamaged in that respect, they get very upset if someone delivers to mailfolders which they already have in their internal buffers. The following recipe assumes that $HOME equals /home/john.

MAILDIR=Mail

:0:/usr/local/lib/emacs/lock/!home!john!Mail!mailbox
* ^Subject:.*whatever
mailbox

and bestie, there's more where that came from โ€” hold on, the rest is coming up next ๐Ÿ’–

Alternatively, bestie, you can have procmail deliver into its own lil set of mailboxes ๐Ÿ’Œ โ€” which you then periodically empty and copy over to your emacs files using movemail. And movemail uses mailbox.lock local lockfiles per mailbox, no cap. This actually is the preferred mode of operation in conjunction with procmail. โœจ


๐ŸŒธ Extracting headers into environment variables

To extract certain headers from a mail and put them into environment variables you can use any of the following constructs:

SUBJECT=`formail -xSubject:`    # regular field
FROM=`formail -rt -xTo:`        # special case

:0 h                            # alternate method
KEYWORDS=| formail -xKeywords:

๐Ÿงน Temp files that clean themselves up

If you are using temporary files in a procmailrc file, and want to make sure that they are removed just before procmail exits, you could use something along the lines of:

TEMPORARY=$HOME/tmp/pmail.$$
TRAP="/bin/rm -f $TEMPORARY"

The TRAP keyword can also be used to change the exitcode of procmail. I.e. if you want procmail to return an exitcode of 1 instead of its regular exitcodes, you could use:

EXITCODE=""
TRAP="exit 1;"   # The trailing semi-colon is important
                 # since exit is not a standalone program

Or, if the exitcode does not need to depend on the programs run from the TRAP, you can use a mere:

EXITCODE=1

๐Ÿ”ฅ Postscript-printing recipes (the glow-up versions)

The following recipe prints every incoming mail that looks like a postscript file.

:0 Bb
* ^^%!
| lpr

The following recipe does the same, but is a bit more selective. It only prints the postscript file if it comes from the print-server. The first condition matches only if it is found in the header. The second condition only matches at the start of the body.

:0 b
* ^From[ :].*print-server
* B ?? ^^%!
| lpr

The same as above, but now by slightly different means:

:0
* ^From[ :].*print-server
{
  :0 B b
  * ^^%!
  | lpr
}

Likewise:

:0 HB b
* ^^(.+$)*From[ :].*print-server
* ^^(.+$)*^%!
| lpr

๐ŸŒ€ The mail-loop-avoidance arc (two accounts, zero chaos)

Suppose you have two accounts, you use both accounts regularly, but they are in very distinct places (i.e., you can only read mail that arrived at either one of the accounts). You would like to forward mail arriving at account one to account two, and the other way around. The first thing that comes to mind is using .forward files at both sites; this won't work of course, since you will be creating a mail loop ๐Ÿ’€. This mail loop can be avoided by inserting the following recipe in front of all other recipes in the $HOME/.procmailrc files on both sites. If you make sure that you add the same X-Loop: field at both sites, mail can now safely be forwarded to the other account from either of them.

:0 c
* !^X-Loop: yourname@your.main.mail.address
| formail -A "X-Loop: yourname@your.main.mail.address" | \
   $SENDMAIL -oi yourname@the.other.account

๐Ÿ“จ Auto-reply fileserver vibes

If someone sends you a mail with the word retrieve in the subject, the following will automatically send back the contents of info_file to the sender. Like in all recipes where we send mail, we watch out for mail loops.

:0
* !^From +YOUR_USERNAME
* !^Subject:.*Re:
* !^FROM_DAEMON
* ^Subject:.*retrieve
| (formail -r ; cat info_file) | $SENDMAIL -oi -t

Now follows an example for a very simple fileserver accessible by mail. For more demanding applications, I suggest you take a look at SmartList (available from the same place as the procmail distribution). As listed, this fileserver sends back at most one file per request, it ignores the body of incoming mails, the Subject: line has to look like "Subject: send file the_file_you_want" (the blanks are significant), it does not return files that have names starting with a dot, nor does it allow files to be retrieved that are outside the fileserver directory tree (if you decide to munge this example, make sure you do not inadvertently loosen this last restriction).

โš ๏ธ okay bestie listen โ€” that last restriction about the fileserver directory tree? Do NOT loosen it by accident when you're out here munging examples. That's the whole security vibe. ๐Ÿ›‘
:0
* ^Subject: send file [0-9a-z]
* !^X-Loop: yourname@your.main.mail.address
* !^Subject:.*Re:
* !^FROM_DAEMON
* !^Subject: send file .*[/.]\.
{
  MAILDIR=$HOME/fileserver # chdir to the fileserver directory

  :0 fhw                   # reverse mailheader and extract name
  * ^Subject: send file \/[^ ]*
  | formail -rA "X-Loop: yourname@your.main.mail.address"

  FILE="$MATCH"            # the requested filename

  :0 ah
  | cat - ./$FILE 2>&1 | $SENDMAIL -oi -t
}

๐Ÿฆ‹ MIME glow-up (quoted-printable + base64 โ†’ 8-bit)

The following example preconverts all plain-text mail arriving in certain encoded MIME formats into a more compact 8-bit format which can be used and displayed more easily by most programs. The mimencode(1) program is part of Nathaniel Borenstein's metamail package.

:0
* ^Content-Type: *text/plain
{
  :0 fbw
  * ^Content-Transfer-Encoding: *quoted-printable
  | mimencode -u -q

     :0 Afhw
     | formail -I "Content-Transfer-Encoding: 8bit"

  :0 fbw
  * ^Content-Transfer-Encoding: *base64
  | mimencode -u -b

     :0 Afhw
     | formail -I "Content-Transfer-Encoding: 8bit"
}

๐Ÿ’… The ".urgent" file moment (lowkey exotic, highkey iconic)

The following one is rather exotic, but it only serves to demonstrate a feature. Suppose you have a file in your HOME directory called .urgent, and the (one) person named in that file is the sender of an incoming mail, you'd like that mail to be stored in $MAILDIR/urgent instead of in any of the normal mailfolders it would have been sorted in. Then this is what you could do (beware, the filelength of $HOME/.urgent should be well below $LINEBUF, increase LINEBUF if necessary):

๐Ÿซถ heads up: keep $HOME/.urgent well below $LINEBUF or bump LINEBUF up, fr fr.
URGMATCH=`cat $HOME/.urgent`

:0:
* $^From.*${URGMATCH}
urgent

๐ŸŒˆ Outgoing-mail filter era

An entirely different application for procmail would be to conditionally apply filters to a certain (outgoing) text or mail. A typical example would be a filter through which you pipe all outgoing mail, in order to make sure that it will be MIME encoded only if it needs to be. I.e. in this case you could start procmail in the middle of a pipe like:

cat newtext | procmail ./mimeconvert | mail chris@where.ever

The mimeconvert rcfile could contain something like (the =0x80= and =0xff= should be substituted with the real 8-bit characters):

DEFAULT=|     # pipe to stdout instead of
              # delivering mail as usual
:0 Bfbw
* [=0x80=-=0xff=]
| mimencode -q

  :0 Afhw
  | formail -I 'MIME-Version: 1.0' \
     -I 'Content-Type: text/plain; charset=ISO-8859-1' \
     -I 'Content-Transfer-Encoding: quoted-printable'

๐Ÿ‘€ SEE ALSO

procmail(1), procmailrc(5), procmailsc(5), sh(1), csh(1), mail(1), mailx(1), uucp(1), aliases(5), sendmail(8), egrep(1), grep(1), biff(1), comsat(8), mimencode(1), lockfile(1), formail(1)


๐Ÿ‘‘ AUTHORS

Stephen R. van den Berg

<srb@cuci.nl>


and that's a wrap on PROCMAILEX(5), bestie ๐Ÿ’– this whole page was brought to you by BuGless, dated whatever that footer date said โ€” go read the other parts, mwah ๐ŸŽ€โœจ