Created 6/12/02; Updated 10/14/02 (Use the information here at your own risk.)

[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.

To Mung Or...

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