So my friend called me about a week ago. He asked me to set up a mailing list service for his domain I manage.
"One thing though: I only want specific people to be able to send e-mail to the list."
Could I have found a system that has a nice GUI and does this and more? Sure. The real question is: was I going to? Absolutely not.
In this article I will tell you how to forward email with python.
On how to forward email to a script in postfix, read my previous post.
You can check my final code on github.
A python script that processes incoming email and forwards it based on the sender and the incoming email's recipient.
After postfix forwards email, we can get it in python through
stdin like so:
import sys email_in = sys.stdin.read()
This will give us a string of the whole email, headers and all.
The next step is to parse it into a
Message object. This will enable us to perform all kinds of email tricks on it (like extracting From and To addresses)
The central class in the email package is the
Messageclass, imported from the
Messageprovides the core functionality for setting and querying header fields, and for accessing message bodies.
Let's do the actual parsing:
from email.parser import Parser incoming = Parser().parsestr(email_in)
incoming is now a
Message object that we can work with.
Getting the headers
Now that we have a
Message object called
incoming, it's pretty simple to get stuff from it, like the sender or recipient of the email:
sender = incoming['from'] this_address = incoming['to']
We will set up our script to look at the original address the email was sent to and decide which mailing list to work with based on that.
We will use the
parseddr function to do that:
from email.utils import parseaddr to_list = parseaddr(incoming['to'])
parseaddr() returns a list of display name and address, for example:
toaddress = ("John Doe", "email@example.com")
That's why we needed the
 at the end, to get the email address as a string.
Now we need to get the part before the @ to see which list the mail was sent to:
list_user = to_list.split('@')
We now have the list name and the sender, so we can go on and perform our checks to see if the list exists and the sender is authorized to use it.
Checking list and sender authorization
Our next step is to check if the list actually exists.
As we will need to parse files twice, let's write a function for that:
def readlist(file_path): with open(file_path, mode="r") as f: lines = f.readlines() result = [i.strip() for i in lines] return result
This simple function reads the contents of the given file and returns a list of text on each line.
Note the list comprehension, this is one of the things why I love python.
First we will try to read the mailing list file:
try: list_members = readlist(list_user + ".list") except FileNotFoundError: bounce(nolist, incoming) #If the file is not found we will send a "bounce" email #stating the list does not exist. #We can write this function later.
Next we will load the authorized senders file:
senders = readlist("senders.list")
Now let's check if the sender is authorized to use this mailing list:
#We get the sender email from the headers the same way #we did with the mailing list address: sender_addr = parseaddr(sender) #Then we perform the test: if sender_str not in senders: bounce(nonauth, incoming) else: #The sender is authorized to send #Here we will send the actual email
Now that we checked if the list exists and that the sender is on our "authorized senders" list it's time to forward the email to all recipients.
First some imports:
import smtplib from email.mime.multipart import MIMEMultipart
We will use
smtplib to send email through SMTP.
MIMEMultipart is for managing
The following goes into the
else: part of our last
for member in list_members: #Iterate through the list of recipients #Create a Message object msg = MIMEMultipart() #Set the payload to what we received msg.set_payload(incoming) #Change headers so From is the list address #and Reply-to is original sender msg['From'] = this_address msg['reply-to'] = sender msg['To'] = member msg['Subject'] = incoming['subject'] #Create an SMTP object on localhost s = smtplib.SMTP('localhost') #Send message, and close the SMTP connection s.send_message(msg) s.quit()
The only thing left is to write the
bounce function we used previously.
The steps will be very similar to how we sent email to the list:
from email.mime.text import MIMEText def bounce(bouncetext, incoming): msg = MIMEMultipart() msg['Subject'] = "Re: " + incoming['subject'] msg['From'] = incoming['to'] msg['To'] = incoming['from'] msg.attach(MIMEText(bouncetext, _charset='UTF-8')) s = smtplib.SMTP('localhost') s.send_message(msg) s.quit() exit(0)
Finally let's see all the imports we used:
import sys from email.parser import Parser from email.utils import parseaddr import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText
Now we add a "shebang" to the first line of our script to make it executable outside the python interpreter:
And we are done. Upload the script to your server and off the emails go.
If you get stuck, it's always wise to read the docs:
I have a tiny newsletter you could subscribe to.
I am also on twitter, see you there.