[Note: Procmail version 3.22 - the, to date, latest stable version - is problematic for the routines presented here. Version 3.22 does not handle the var=| script construct properly, and results in the 'Out of memory' error, and bouncing the associated email. Reports are that Procmail's author has not yet admitted that this is a problem. In the mean time, pre version 3.22 Procmail stable versions are recommened instead - I'm running the routines with stable version 3.15.]
Forward
So, is everyone having fun with their procmail projects today? - bare with me, I've been up more than 24.
Frequenting the SA, Razor mailing lists, I put one of those tags at the end of my mails pleading that I not be CCed on list replies. Instead of obliging, I was told to simply take care of the problem with procmail - that is formail -D. That was easy enough, and I appreciated the advice. After all, the list subject was heavily related to mail processing, so I felt obliged to learn a bit about it. And I went on and fixed that pesky list reply problem that my Netscape mail version didn't.
Now when I hit the reply button, the list address, regardless of what
was in the mail's header when I received it, handily appears in the To:
field. I was turned on to this idea by one of the list subscribers'
rants against reply-to munging, which I've come to know, is what I've done
- forgive me for I have munged, and I'm loving it.
Won't you mung with me? Ok, let's get to know each other a little
better first.
Lists Reply-To w/Procmail
Believe it or not, some people still have mail programs that fail to
make it easy to reply to mailing
list mail. You'd like to be able to hit the reply button, and
be guarenteed that one of your options is the mailing list to which you
are replying. But, not all mailers do this. Mine doesn't -
it's old. And It doesn't help that many fellow subscribers insist
on CCing you in reply to your posts.
That's why I wrote these procmail routines - they set a Reply-To: header to all incoming list mail, so when you hit the reply button, the list address appears in your email client's To: field.
How To
The routines are (I think) very easy to use - set HEADER_MANIP to the directory in which you place the routines. And set the ADDRESSFILE variable to a file containing list email addresses - one per line - to which to apply reply-to munging. So you would add to your .procmailrc:
HEADER_MANIP=$HOME/ProcMailStuff/ReplyToMung
ADDRESSFILE=$HEADER_MANIP/mailinglists.txt
INCLUDERC=$HEADER_MANIP/replytomungallrc
Download
Below, are the "routines" (procmail rc files) to apply reply-to "munging" to incoming list mail. You can just cut and paste them into correspondingly named files, or better yet, download a zip of the files ftp://wecs.com/procmail/mung.zip. This file will unzip to a directory called /mungdist.
Just one more thing. In all fairness (probably an understatement), it should be noted, even stressed, that there are many "netizens" with strong opinions about Reply-To munging. And the strongest, and perhaps even most compelling, are those against the practice.
I justify the practice because I'm only doing it relative to my own email account. And secondly, I preserve any Reply-To information that was in the mail before I munged it. The routines are very well behaved.
More on Reply-To munging: http://www.google.com/search?q=reply-to+considered+harmful.
Send comments, criticisms, suggestions to (Bryan Hoover) bhoover@wecs.com.
Synopsis
For each "sender" header field, call a routine to test whether it is
a list mail address contained in
ADDRESSFILE, and if it is, set a Reply-To: header to point to this
list address.
Two additional routines were added in an update. These are a "recursive" routine, and its driver, that keep our reply-to header from accumulating duplicates, and clean of non-list addresses - excepting those already contained in reply-to.
Features
The routines will:
Find list addresses in any email header field
Handle multiple list addresses in a single email
Retain pre-existing Reply-To field value
Save original Reply-To in field Old-Reply-To
Retain any pre-existing Old-Reply-To headers
And of course, set Reply-To header to list addresses
First Routine (Top Level - calls routine 2), replymungallrc
# Driver for reply-to header munging - checks headers for addresses
in
# file pointed to by ADDRESSFILE and sets the reply-to header to point
# to these addresses. So when, in your email client, you press
the reply
# button, the To field will be filled with any addresses that appeared
in
# the header and were also in ADDRESSFILE.
#
#In addition to the pre-existing reply-to preservation done by .setheadertagrc,
we
#save the pre-existing reply-to in Old-Reply-To
#
# Requires HEADER_MANIP set to directory containing this file, and
its
# accompning/required files.
#
# This .setheadertagrc driver, .replytomungallrc, is a bit different
from
# .replytomungrc. It does not stop at the first successfull header
match,
# but rather, mungs all matching headers, so that if, for instance,
there
# is both a To: match, and a CC: match, both will be added to the reply-to.
# get original reply-to header value, if any, we'll add this back
# as Old-Reply-To when finished processing
:0i
OrigReplyTo=|$FORMAIL -zxReply-To:
# Tell .setheadertagrc to extract the Sender: tag value
HEADERTAG=Sender:
# If it's one of the mailing lists we subscribe to,
# set reply-to tag to it
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
# And so on for each "To" tag in the header
HEADERTAG=CC:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=From:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=Resent-By:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=Resent-From:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=Resent-Sender:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=Resent-To:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=Reply-To:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
HEADERTAG=To:
INCLUDERC=${HEADER_MANIP}/.setheadertagrc
# get the Reply-To resulting from above processing
:0i
CURRENTREPLYTO=|$FORMAIL -zxReply-To:
# add back as Old-Reply-To, the original Reply-To, if any, saved
# before our processing. make sure we have a new Reply-To from
# our processing, else no need to add
:0f
* ! OrigReplyTo ?? $CURRENTREPLYTO
* ! CURRENTREPLYTO ?? ^^^^
* ! OrigReplyTo ?? ^^^^
|$FORMAIL -i "Old-Reply-To: $OrigReplyTo"
Other (second) Routine, setheadertagrc
# Input:
#
# ADDRESSFILE - contains email addresses we will use to grep a HEADERTAG
val
#
# HEADERTAG - an email header field, such as From:, Sender:, To:, etc.
#
#
# Output:
#
# Success or fail, as indicated with recipe flags E, A, e, a.
# We need a recipe that will return success or fail (for subsequent
a/e
# recipe conditional evaluation) relative to whether current header
val
# (header field denoted in HEADERTAG) is an address in our file of
list
# addresses. We also need to put the current header val into
a file so
# we can grep with it as the supper string, and our list file addresses
# as the substrings. Durring all this, the header value file
must stay
# locked. To accomplish all this, it's neccessary to have rm,
and echo
# in corresponding recipe conditionals, as seen below.
# Get the header field value of the header field denoted by input parameter
# HEADERTAG, and remove newlines, squeeze whitespace
:0i
HEADERTAGVAL=|$FORMAIL -zx$HEADERTAG | tr -d "\n" | tr -s " "
# If the HEADERTAGVAL is not empty, clear our greping target file,
# headertagfile, then rewrite it to contain HEADERTAGVAL, and
# grep to see if any of the list addresses in ADDRESSFILE, are
# substrings of HEADERTAGVAL. If succeed, set a reply-to header
# to the list denoted by HEADERTAGVAL.
LOCKFILE=replytomung.lock
:0
* ! HEADERTAGVAL ?? ^^^^
{
# clear header greping file
:0iwc
|rm -f ${HEADER_MANIP}/headertagfile
# just for fun, keep a list of headers that happen by
:0iwc
|echo "$HEADERTAG>>$HEADERTAGVAL">>${HEADER_MANIP}/headertagall
# write header greping file
:0iwc
|echo "$HEADERTAGVAL">>${HEADER_MANIP}/headertagfile
# one of our list addresses in header?
:0
* ? cat ${HEADER_MANIP}/headertagfile | grep -i -f $ADDRESSFILE
{
# we've got a match, and again for fun, write it to a file
:0iwc
|echo "$HEADERTAG>>$HEADERTAGVAL">>${HEADER_MANIP}/headertagmatched
# Preserve any pre-existing repy-to address
# And make sure .extracttargetsrc does not
# add addresses already in reply-to
CURRENTREPLYTO
:0i
CURRENTREPLYTO=|$FORMAIL -zxReply-To: | tr -d "\n"
| tr -s " "
# pass to .extract routine, addresses already
# in reply-to header so to be excluded from
# adding to reply-to header
:0iwc
|rm -f ${HEADER_MANIP}/headerexfile
:0iwc
|echo "$CURRENTREPLYTO">>${HEADER_MANIP}/headerexfile
# We've got a header containing one of our list
# addresses. Now, remove any non-list addresses
# from the header, and exclude anything already
# in reply-to
INCLUDERC=${HEADER_MANIP}/.extracttargetsrc
:0
* CURRENTREPLYTO ?? ^^^^
{
:0 f
| $FORMAIL -I "Reply-To: $HEADERTAGVAL"
}
:0E
{
# .extracttargetsrc exclude?
:0
* ! HEADERTAGVAL ?? ^^^^
{
:0 f
| $FORMAIL -I "Reply-To:
$CURRENTREPLYTO,$HEADERTAGVAL"
}
}
}
}
LOCKFILE
Additional Routine, .extracttargetsrc
Basically a driver for .doextractargetsrc. We set a couple variables, call .doextracttargetsrc, and then set our return value (our caller's input variable) HEADERTAGVAL to .doextracttargetsrc's return value.
:0ic
|cp ${HEADER_MANIP}/headertagfile ${HEADER_MANIP}/curheadertagfile
:0
{
extractCount=0
cummedMatch=""
INCLUDERC=${HEADER_MANIP}/.doextracttargetsrc
# assign output to in/out var
HEADERTAGVAL=$cummedMatch
}
Additional Routine 2, .doextracttargetsrc
We're given a header with at least one email address in our file of target (list) addresses. The header could also contain non-list addresses. Extract and return, only the list addresses in the header.
Also, we're given a file containing exception email addresses. These are addresses that are in address file, but for whatever reason, should be excluded from our return value, so we extract these as well.
# Input:
#
# curheadertagfile - disk file containing a header value - one or more
# email addresses, with at least one being an email list address from
in
# ADDRESSFILE
#
# ADDRESSFILE - disk file contains email addresses we will use to grep
a
# header
#
# headerexfile - exclusion file - exclude addresses that, though in
# ADDRESSFILE, are also in headerexfile
#
# extractCount - set to 0
#
# cummedMatch - set to ^^^^
#
# Output:
#
# cummedMatch - comma delimited mailing list addresses extracted from
# curheadertagfile
:0
{
:0
* $ ${extractCount:-0}^0
* 1^0
{ extractCount=$= }
# get the next address from ADDRESSFILE
:0i
curAddress=|cat $ADDRESSFILE|sed -e ${extractCount}q -ed
# past end of file?
:0
* ! curAddress ?? ^^^^
{
# this address in header?
:0i
curMatch=|grep -i $curAddress ${HEADER_MANIP}/headertagfile
:0
* ! curMatch ?? ^^^^
{
# this address in exclusion file?
:0
* ! ? grep -i $curAddress ${HEADER_MANIP}/headerexfile
{
:0
* cummedMatch ?? ^^^^
{
cummedMatch=$curAddress
}
:0E
{
cummedMatch="$cummedMatch,$curAddress"
}
}
}
# tail recursion 'til curAddress ?? ^^^^
INCLUDERC=.doextracttargetsrc
}
}
Copyright (C) 2002, Bryan Hoover