Home / Alt manpages / procmail(1)

  • procmail(1)
  • User command
  • linux

Sort incoming mail with procmail recipes safely

By the end of this guide, a message from a chosen sender will be filed by a procmail recipe, and you will have tested the rule against a temporary mailbox rather than risking your live mail. Allow about 20 minutes, including the dry run and inspection of the log.

Before you start

This guide uses procmail 3.24, installed here as Ubuntu package 3.24-1ubuntu2. It expects a local procmail installation and a shell account whose home directory you control. You do not need root for a personal ~/.procmailrc. A system-wide /etc/procmailrc, mail transport integration, or explicit delivery for another user requires an administrator and is outside this safe first test.

Procmail reads a message from standard input. With no rcfile argument it looks for $HOME/.procmailrc; if processing reaches the end without delivering the message, it falls back to the default mailbox. Its defaults can override environment variables supplied before startup, so set test values as command-line assignments when using -m.

1. Make a test filter

Do not begin by editing the file used by your live mail delivery. Create a separate rcfile and two empty destinations under /tmp. The example has one recipe: it examines the header by default and files messages whose From: header contains the example address.

testdir=$(mktemp -d /tmp/procmail-test.XXXXXX)
mkdir -p "$testdir/maildir"
cat > "$testdir/filter.rc" <<'EOF'
LOGFILE=$MAILDIR/procmail.log
VERBOSE=yes

:0:
* ^From:.*[email protected]
alerts
EOF

The trailing colon on :0: asks procmail to use a local lockfile for the destination. The condition begins with *; conditions are combined with AND, and header matching is the default. A plain filename is an mbox-style file relative to MAILDIR, while a directory ending in / is treated as a maildir.

Checkpoint

Confirm that the rcfile contains the recipe and that the test directory is not a path you care about:

sed -n '1,20p' "$testdir/filter.rc"
find "$testdir" -maxdepth 2 -type d -print

2. Feed it a harmless message

Use -m to select the test rcfile. The two assignments appear after the defaults, which makes the temporary paths authoritative for this run. The input is a complete, ordinary message with a blank line separating its header and body.

printf 'From: [email protected]\nTo: [email protected]\nSubject: Server alert\n\nDisk space is low.\n' \
  | procmail -m "MAILDIR=$testdir/maildir" \
      "DEFAULT=$testdir/maildir/inbox" "$testdir/filter.rc"

There should now be an alerts file and a log. The message is consumed by the matching recipe, so it should not also appear in inbox.

find "$testdir/maildir" -maxdepth 1 -type f -printf '%f\n' | sort
sed -n '1,12p' "$testdir/maildir/alerts"
tail -n 8 "$testdir/maildir/procmail.log"

Expected file names are:

alerts
procmail.log

The log should mention the destination and a successful folder delivery. Exact timestamps and byte counts vary. VERBOSE=yes is useful while developing a rule; remove it or set VERBOSE=no when you no longer want per-message logging.

3. Add a second rule without losing unmatched mail

Recipes are considered in order. A delivering recipe normally ends processing for that message, so put specific rules before broad ones. Add a newsletter rule, then a final default delivery. The default keeps messages that match neither earlier condition.

:0:
* ^Subject:.*newsletter
news

:0
$DEFAULT

The last action uses the DEFAULT variable rather than duplicating a path. This example deliberately leaves the default action without a local lockfile because the normal default mailbox has its own delivery semantics. If several processes write the same ordinary file, use a recipe lock and confirm the destination is suitable for mbox delivery.

To check the new branch, feed a message with Subject: Weekly newsletter and look for news. Feed a third message with neither marker and look for inbox. Keep each test message disposable.

4. Move the rule into your real rcfile carefully

Warning

A recipe changes mail delivery immediately. A typo can divert mail, invoke a program, forward private content, or discard a message. Keep a copy of your current rcfile, add one tested recipe at a time, and start by delivering to a folder you can inspect. Do not use /dev/null, a forwarding action beginning with !, or a pipe beginning with | until the matching condition has been tested.

cp -- "$HOME/.procmailrc" "$HOME/.procmailrc.before-mail-rule"
mkdir -p "$HOME/mail"
cat >> "$HOME/.procmailrc" <<'EOF'

# File alerts after confirming the condition in a temporary mailbox.
:0:
* ^From:.*[email protected]
mail/alerts
EOF

In this form MAILDIR defaults to $HOME, so mail/alerts means a file below your home directory. Ensure the rcfile is private enough for procmail's security checks: it must be owned by you or root, and your home rcfile must not be group-writable or live in a group-writable directory.

Undo this exact change by restoring the saved copy:

cp -- "$HOME/.procmailrc.before-mail-rule" "$HOME/.procmailrc"

That recovery step replaces later edits too, so use it only if the backup was made immediately before this recipe was added. Otherwise remove just the new recipe in an editor, then send yourself a known test message and inspect the destination.

Common traps

  • Header expressions are case-insensitive unless the recipe has the D flag. Conditions are passed to procmail's internal extended regular expression engine, not to a shell, so comments cannot be placed on the condition line.
  • A message can silently lose its header or body if a delivering recipe uses only the h or b flag without c. Leave those flags alone until you have a specific filtering need.
  • A pipe is a program action, not a harmless preview. Add w when you need procmail to wait for the program and check its exit status. Treat shell expansion, forwarding loops, and autoreplies as security-sensitive.
  • For weighted scoring, prefix a condition with a value such as 10^1. The recipe matches only when its final score is positive. Use $= in verbose diagnostics to inspect the score, and read procmailsc(5) before composing a scoring rule.

Done means

  • The installed version is known: procmail reports 3.24 2022/03/02.
  • A temporary message was filed by a matching recipe and verified in the log.
  • Unmatched mail has an explicit, reviewed default destination.
  • The live rcfile has a backup, a tested change, and a recovery path.
  • No recipe forwards, pipes, or discards mail until its condition is proven.