So you want to process e-mail from Python? (tummy.com, ltd. Journal Entry)
tummy.com: we do linux

Thursday October 23, 2008 at 22:49
Subject: So you want to process e-mail from Python?
Keywords: E-mail, Python, Technical
Posted by: Sean Reifschneider

The other day I had two different clients asking about processing e-mail from a python program. In particular, each e-mail message that comes in gets handed off to be processed by this program. Setting up the mail server to call the program is fairly easy, by configuring local delivery and using the .forward file or similar. However, the program which processes the message needs to do a number of things to do it's job reliably.

I pulled together some pieces of existing code I had to handle these issues. Read on for an example of my code for processing e-mail.

The main responsibilities that need to be handled are:

  • Report if a Traceback happens.
  • Give the system administrator the opportunity to correct Tracebacks and re-process the message before bouncing it.
  • Log errors and tracebacks.
  • Parse the incoming message.
  • Report success when the message is completely processed.

This sample includes the ability to report tracebacks via syslog directly, by providing information back to the mail server (via stderr, which many mail servers honor), and by e-mailing a traceback report.

Below is the code I came up with, the highlighted portion is where the custom processing would be done.

This source can be downloaded from the ftp.tummy.com FTP site under /pub/tummy/pythonmailrcpt. This code is placed in the public domain, feel free to use it.

Mail processing example:

  1. #!/usr/bin/env python
  2. #
  3. #  Python program which receives e-mail.
  4.  
  5. import rfc822, syslog, sys, random, os
  6.  
  7.  
  8. #################
  9. class ExceptHook:
  10.    ########################################################################
  11.    def __init__(self, useSyslog = 1, useStderr = 0, emailRecipient = None):
  12.       self.useSyslog = useSyslog
  13.       self.useStderr = useStderr
  14.       self.useEmail = emailRecipient
  15.  
  16.  
  17.    #######################################
  18.    def __call__(self, etype, evalue, etb):
  19.       import traceback, string
  20.       tb = traceback.format_exception(*(etype, evalue, etb))
  21.       tb = map(string.rstrip, tb)
  22.       tb = string.join(tb, '\n')
  23.  
  24.       if self.useEmail:
  25.          emailFp = os.popen('/usr/sbin/sendmail -t -oi', 'w')
  26.          emailFp.write('To: %s\n' % self.useEmail)
  27.          emailFp.write('From: %s\n' % self.useEmail)
  28.          emailFp.write('Subject: Traceback notification from %s\n' %
  29.                os.path.basename(sys.argv[0]))
  30.          emailFp.write('\n')
  31.       else: emailFp = None
  32.  
  33.       for line in string.split(tb, '\n'):
  34.          if emailFp: emailFp.write(line + '\n')
  35.          if self.useSyslog: syslog.syslog(line)
  36.          if self.useStderr: sys.stderr.write(line + '\n')
  37.       if emailFp: emailFp.close()
  38.  
  39.       sys.stderr.write('Error in processing e-mail.\n')
  40.  
  41.       #  TEMPFAIL causes the delivery to be retried
  42.       sys.exit(os.EX_TEMPFAIL)
  43.  
  44. #################################
  45. #  logging and exception handling
  46. syslog.openlog(os.path.basename(sys.argv[0]), syslog.LOG_PID, syslog.LOG_MAIL)
  47. sys.excepthook = ExceptHook(useSyslog = 1, useStderr = 0,
  48.       emailRecipient = 'user@example.com')
  49.  
  50. #  parse the incoming message
  51. #(For Python versions <2.4)
  52. #import rfc822
  53. #headers = rfc822.Message(sys.stdin)
  54. #(For Python versions >= 2.4)
  55. import email.parser
  56. headers = email.parser.Parser().parse(sys.stdin)
  57.  
  58. #  process the message here
  59. syslog.syslog('Received mail from "%s", subject: "%s"' % (
  60.       headers.get('From'), headers.get('Subject') ))
  61.  
  62. #  demonstration of what happens when there's an error
  63. #  generate an error 50% of the time
  64. if random.choice([ True, False ]): raise NotImplementedError('Test exception')
  65.  
  66. #  successful return code
  67. sys.exit(0)

(Post Reply)