This is me, Wu!
» e-shell.org
In this page
Rate this page!
Search inside the wiki!
  Home >> hacks >> Quick mailing list using Sendmail and Python

Quick mailing list using Sendmail and Python

The problem: you need a small mailing list

So, you would like to have all the benefits of a mailing list without the extra-bloat from MailMan or Majordomo... Then this hack is for you!

Some days ago, I needed to create some kind of a little mailing list, so some friends of mine could communicate themselves using a single email address instead of filling up all the To: and Cc: fields.

At first, I thought about using aliases. It was pretty easy, just add a line like:

mailing: friend_a@example.net, friend_b@example.net, friend_c@example.net, friend_d@example.net

to the /etc/mail/aliases file, and use newaliases to rebuild the aliases database. That's a solution, and it works fine, as soon as anyone send an email to mailing@example.net, the mail will be redirected to all the addresses in the list. But that solution has a problem, when anyone would like to reply to an email from that alias, he/she/it would have to use the reply to all button in his/her/its MUA, if not, the answer would be sent only to the address in the From: header (the one that sent that email previously to the alias address.

So, using aliases, I solved half of the problem only. I searched through the Internet and I a look in both the Sendmail Book (AKA the bat book) and the Sendmail CookBook from O'reilly (both of them have some nice tricks about using aliases in Sendmail), but I didn't find what I was searching for.

Then, I remembered one thing I noticed in the aliases man page:

Command
  |command

  A command starts with a pipe symbol (|), it receives messages via stan-
  dard input.

mmm, it seems like emails sent to an alias address could be re-sent through a pipe to a given command... in fact (after reading a little bit about it) seems like Majordomo works that way...

The solution: aliases + python script

Well, time to write some useful code then. After taking a look over the Python library reference I wrote that tiny little script:

#!/usr/local/bin/python
#
# Little script to re-send emails coming from sendmail
# aliases
#
# Wu | http://www.e-shell.org
# Mon Oct 29 21:34:34 CET 2007

import sys, email, smtplib

try:
    raw_email = sys.stdin.read()

except:
    raise SystemExit

email_addresses = ['friend_a@example.net', 'friend_b@example.net', 'friend_c@example.net', 'friend_d@example.net']
email_replyto = 'mailing@example.net'

try:
    tmpfile = file('/tmp/SendToMailing', 'w')
    tmpfile.write(raw_email)
    tmpfile.close()

except:
    raise SystemExit

try:
    email_file = file('/tmp/SendToMailing', 'r')
    email_data = email.message_from_file(email_file)
    email_file.close()

except:
    raise SystemExit

email_data['Reply-to'] = email_replyto

try:
    smtp_conn = smtplib.SMTP()
    smtp_conn.connect()
    smtp_conn.sendmail(email_data['From'], email_addresses, email_data.as_string())
    smtp_conn.close()

except:
    raise SystemExit

Easy, isn't it?

Let me explain some things about the script. first:

#!/usr/local/bin/python

This is needed for the script to be interpreted by the python interpreter correctly. Probably, you have see something like:

#!/usr/bin/env python

instead in some other python scripts. That will result in Sendmail complaining about some internal mailer errors while trying to execute the script, so don't use it here.

try:
    raw_email = sys.stdin.read()

except:
    raise SystemExit

Ok, that one is easy, as we can read in the aliases man page, A command starts with a pipe symbol (|), **it receives messages via standard input*.* So, we have to read the standard input (stdin) to get the email message.

Once we have read the email, the script saves it in some random file somewhere in the filesystem (/tmp/SendToMailing? here), and then we use the email module from python to get the contents of that file and put them inside an email object :

try:
    tmpfile = file('/tmp/SendToMailing', 'w')
    tmpfile.write(raw_email)
    tmpfile.close()

except:
    raise SystemExit

try:
    email_file = file('/tmp/SendToMailing', 'r')
    email_data = email.message_from_file(email_file)
    email_file.close()

except:
    raise SystemExit

Next, the proper Reply-To header is added to the email object, and finally we use the smtplib module to send the mail to its final destination (the list of addresses in the mailing list):

try:
    smtp_conn = smtplib.SMTP()
    smtp_conn.connect()
    smtp_conn.sendmail(email_data['From'], email_addresses, email_data.as_string())
    smtp_conn.close()

except:
    raise SystemExit

The script needs all the try: ... except: checks because of Sendmail. If the script doesn't exit normally (for example, if an error happened while open the file for writing the email message), Sendmail will abort the process with some ugly errors. So, to avoid that, I just added some failure-control in the critical points of the script.

Fine, now we have the script, but we have to tell Sendmail that it should send emails coming for mailing@example.net to that script. Let's edit again the /etc/mail/aliases file, changing our mailing alias:

mailing: "|/usr/local/bin/SendToMailing"

After running newaliases again, Sendmail should begin to send the messages to our script but, if we have a secure Sendmail configuration, it will not work.

By default since one of the 8.11.x version, Sendmail uses a built-in shell to limit access from Sendmail to system-wide binaries. This shell is smrsh (Sendmail Restricted Shell). That means that only allowed binaries could be executed from Sendmail directly (as we are trying to do here). To add our little script to the allowed binaries, we have to create a symlink to the script, inside the /usr/libexec/sm.bin directory:

# ln -s /usr/local/bin/SendToMailing /usr/libexec/sm.bin/SendToMailing
# ls -l /usr/libexec/sm.bin/
total 0
lrwxr-xr-x  1 root  wheel  28 Oct 29 20:48 SendToMailing -> /usr/local/bin/SendToMailing
#

Now it should work, and your little mailing list should be working and if you check the /var/log/maillog log, you should notice something like:

Oct 30 10:58:40 tyr sm-mta[27454]: l9U9wcln007289: alias <mailing@example.net> => "|/usr/local/bin/SendToMailing"
Oct 30 10:58:48 tyr sm-mta[27454]: l9U9wcln007289: to="|/usr/local/bin/SendToMailing", ctladdr=<mailing@example.net> (1/0), delay=00:00:09, xdelay=00:00:08, mailer=prog, pri=31727, dsn=2.0.0, stat=Sent

when a new email for mailing@example.net arrives.