A mailing list alternative to Mailman using cPanel Pipe to a Program and Python

Recently my web hosting provider decided they were no longer going to support GNU Mailman on their cPanel installation “for performance reasons”. This was annoying as I’d been using Mailman for years to host an internal team mailing list.

When I asked my provider for an alternative, they suggested creating an email forwarder rule for each recipient, ie. allstaff@blah.com forwards to jim@blah.com, jessica@blah.com etc. While this worked as a crude replacement, there was one big downside: replying to an email would reply to the original sender unless you remembered to hit Reply to All.

Feeling left high and dry by some crappy customer service, I wondered if I could hack together my own poor man’s mailing list alternative using Python.

I did some digging and discovered that cPanel allows creation of advanced email forwarders that pipe to a program on the server. There’s a great how-to guide for setting this up. On Stack Overflow I learned how to pipe an email from standard input into a Python program.

This was starting to look promising. If I could get cPanel to pipe emails to a Python script on the server, all I’d need to do is twiddle some message headers in the script then send out the adjusted emails to the mailing list recipients.

I looked into what was so special about the mailing list email headers generated by Mailman. It turns out the magic fairy dust I needed was simply the inclusion of a ‘Reply-To’ field.

So I got a hackin’ and threw together a Python script that looked something like this:

#! /usr/bin/python

mailing_list_subject_prefix = '[MAILING_LIST] '
mailing_list_recipients = ['your@email.com', 'another@email.com']

import sys
import email
import smtplib

# Read in the email from standard input
msg =  email.message_from_string(sys.stdin.read())

# Set the Reply-To: field for the outgoing mailing list emails, and append a mailing list prefix to the subject if it's not there
def add_replace_header(msg, header, value):
    try:
        msg.replace_header(header, value)
    except KeyError:
        msg.add_header(header, value)
add_replace_header(msg, 'Reply-To', msg['To'])
if mailing_list_subject_prefix not in msg['Subject']:
    add_replace_header(msg, 'Subject', mailing_list_subject_prefix + msg['Subject'])

# Send the modified email out to all mailing list recipients.
# In case of an error, print the exception and dump the message to standard output. This will trigger a bounce email with the error contents for debugging.
try:
    server = smtplib.SMTP('localhost')
    server.sendmail(msg['To'], mailing_list_recipients, msg.as_string())
    server.quit()
except Exception, e:
    print str(e)
    print msg.as_string()

Hopefully this is helpful to anyone else out there in a similar predicament. Just tweak this script, upload it to your server, mark it as executable (chmod +x), then set up a cPanel advanced email forwarder rule to pipe to the script.

You might need to change the Python path at the start of the script or tweak the SMTP login method depending on how your server is set up. Refer to the linked articles above for further technical details.

I miss Mailman. :(

Author: Michael Davies

Senior software engineer with 15 years of commercial video games experience.