This is a simple python script that can be used to implement a simple mailing list. All it does is retrieve all messages from a mailbox, process subscriptions and unsubscriptions, forward remaining messages to subscribed users and delete the messages from the mailbox. To actually make the mailing list working, you have to run this periodically – for example every hour.
import poplib
import smtplib
class PopMailList:
members_file = 'members.list'
def __init__(self, address, password):
self.address = address
self.user, self.host = address.split('@', 1)
self.password = password
try:
self.members = file(self.members_file).readlines()
except IOError:
self.members = []
def process_all_messages(self):
pop = poplib.POP3(self.host)
pop.user(self.user)
pop.pass_(self.password)
status, messages, size = pop.list()
number = 0
for info in messages:
number, size = map(int, info.split(' ', 1))
status, msg, size = pop.retr(number)
self.process_message(msg)
pop.dele(number) # we delete the processed messages
pop.quit()
print "%d messages processed" % number
def process_message(self, msg):
fromaddr = ''
toaddr = ''
subject = ''
for line in msg:
if line.startswith('Subject:'):
subject = line.split(':', 1)[1].strip()
elif line.startswith('From:'):
fromaddr = line.split(':', 1)[1]
fromaddr = fromaddr[fromaddr.find('<')+1:fromaddr.find('>')].strip()
elif line.startswith('To:'):
toaddr = line.split(':', 1)[1]
toaddr = toaddr[toaddr.find('<')+1:toaddr.find('>')].strip()
if toaddr != self.address:
return # skip any messages not directed to the list
if fromaddr not in self.members:
if subject.startswith('subscribe'):
print "subscribing %s" % fromaddr
self.subscribe_member(fromaddr)
else:
print "rejecting %s" % fromaddr
self.reject_message(fromaddr)
else:
if subject.startswith('unsubscribe'):
print "unsubscribing %s" % fromaddr
self.unsubscribe_member(fromaddr)
else:
print "forwarding %s" % fromaddr
self.forward_message(msg)
def subscribe_member(self, address):
self.members.append(address)
self.save_members()
self.send_message(address,
["", "Welcome to the %s mailing list!" % self.address], "Welcome")
def unsubscribe_member(self, address):
self.members.remove(address)
self.save_members()
self.send_message(address,
["", "You unsubscribed from %s." % self.address], "Good bye")
def save_members(self):
file(self.members_file, 'w').writelines(self.members)
def reject_message(self, address):
self.send_message(address,
["", "You need to subscribe to %s by sending" % self.address,
"empty message with 'subscribe' in the subject to be able to post here."],
"Rejected")
def forward_message(self, msg):
new_msg = []
for line in msg:
if (line.startswith('To:') or
line.startswith('Received:') or
line.startswith('Delivered-To:') or
line.startswith('X-Original-To:') or
line.startswith('Return-path:')):
pass
else:
new_msg.append(line)
for member in self.members:
print 'sending to %s' % member
self.send_message(member, new_msg)
def send_message(self, address, msg, subject=None):
if subject:
msg = ["Subject: %s" % subject] + msg
msg.insert(0, "To: %s" % address)
msg.insert(0, "Reply-To: %s" % self.address)
server = smtplib.SMTP(self.host, 25)
server.login(self.user, self.password)
server.sendmail(self.address, address, '\r\n'.join(msg))
server.quit()
ml = PopMailList('mailbox@mailhost', 'password')
ml.process_all_messages()
The member list is kept in a “members.list” file (we don’t do any locking, but pop3 lock the mailbox for us, so only one instance of the script can run at a time).
I assumed that the addresses of the mailbox, the pop3 server and the smtp server are all the same, and that the smtp server requires authentication.
The subscription and unsubscription logic is based on the message’s subjects, but could be somewhat improved too (e.g. don’t forward the ‘subscribe’ messages if the member is already subscribed).
The sending of messages is done in a highly suboptimal way – we log in to the server and authenticate for every single message – this can be improved. A little more effective version follows:
import poplib
import smtplib
class PopMailList:
members_file = 'members.list'
def __init__(self, address, password):
self.address = address
self.user, self.host = address.split('@', 1)
self.password = password
# read member list from file
try:
self.members = file(self.members_file).readlines()
except IOError:
self.members = []
# open connection to pop3 server
self.pop3 = poplib.POP3(self.host)
self.pop3.user(self.user)
self.pop3.pass_(self.password)
# open connection to smtp server
self.smtp = smtplib.SMTP(self.host, 25)
self.smtp.login(self.user, self.password)
def close(self):
self.pop3.quit()
self.smtp.quit()
def process_all_messages(self):
status, messages, size = self.pop3.list()
number = 0
for info in messages:
number, size = map(int, info.split(' ', 1))
status, msg, size = self.pop3.retr(number)
self.process_message(msg)
self.pop3.dele(number) # we delete the processed messages
print "%d messages processed" % number
def process_message(self, msg):
# read the headers
fromaddr = ''
toaddr = ''
subject = ''
for line in msg:
if line.startswith('Subject:'):
subject = line.split(':', 1)[1].strip()
elif line.startswith('From:'):
fromaddr = line.split(':', 1)[1]
fromaddr = fromaddr[fromaddr.find('<')+1:fromaddr.find('>')]
elif line.startswith('To:'):
toaddr = line.split(':', 1)[1]
toaddr = toaddr[toaddr.find('<')+1:toaddr.find('>')]
elif line == "":
break;
# decide what to do
if fromaddr not in self.get_members():
if subject.startswith('subscribe'):
print "subscribing %s" % fromaddr
self.subscribe_member(fromaddr)
else:
print "rejecting %s" % fromaddr
self.reject_message(fromaddr)
else:
if subject.startswith('unsubscribe'):
print "unsubscribing %s" % fromaddr
self.unsubscribe_member(fromaddr)
else:
print "forwarding %s" % fromaddr
self.forward_message(msg)
def subscribe_member(self, address):
self.get_members()
self.members.append(address)
self.save_members()
self.send_message(address,
["", "Welcome to the %s mailing list!" % self.address], subject="Welcome")
def unsubscribe_member(self, address):
self.get_members()
self.members.remove(address)
self.save_members()
self.send_message(address,
["", "You unsubscribed from %s." % self.address], subject="Good bye")
def save_members(self):
file(self.members_file, 'w').writelines(self.members)
def reject_message(self, address):
self.send_message(address,
["", "You need to subscribe to %s by sending" % self.address,
"empty message with 'subscribe' in the subject",
"to be able to post here."], subject="Rejected")
def forward_message(self, msg):
new_msg = []
for line in msg:
if (line.startswith('To:') or
line.startswith('Received:') or
line.startswith('Delivered-To:') or
line.startswith('X-Original-To:') or
line.startswith('Return-path:')):
pass
else:
new_msg.append(line)
for member in self.get_members():
print 'sending to %s' % member
self.send_message(member, new_msg)
def send_message(self, address, msg, subject=None):
if subject is not None:
msg.inser(0, "Subject: %s" % subject)
head = "To: %s\r\nReply-To: %s\r\n" % (address, self.address)
self.smtp.sendmail(self.address, address, head + '\r\n'.join(msg))
ml = PopMailList('mailbox@mailhost', 'password')
ml.process_all_messages()
ml.close()
This is an extremely simplified mailing list server. It’s hardly useful for anything but a proof of concept. Don’t use even at your own risk 
import smtplib
import smtpd
import asyncore
class MailListServer(smtpd.SMTPServer):
"""
Implements a braindead mailing list server.
Warning! This code is for educational purposes only, don't use it
for running real mailing lists! It's very insecure!
"""
members = []
def process_message(self, peer, mailfrom, rcpttos, data):
"""Receive messages."""
# Automatically subscribe anyone who posts a message
if mailfrom not in self.members:
members.append(mailfrom)
# Forward the messages to all members
for member in self.members:
self.send_message([member], data)
def send_message(self, rcpttos, data):
"""Send a message using a real smtp server somewhere."""
fromaddr = "your@mail.address"
msg = "From: %s\r\nTo: %s\r\n\r\n%s" % (
fromaddr,
", ".join(rcpttos),
data
)
relay = smtplib.SMTP('your.mail.server', 25)
relay.sendmail(fromaddr, toaddrs, msg)
relay.quit()
if __name__=='__main__':
server = MailListServer(('', 25), None)
try:
asyncore.loop(timeout=2)
except KeyboardInterrupt:
server.close()