diff --git a/Doxyfile b/Doxyfile
index 508f65409f5b62374bf2ce568252f03c0d621971..ad35549f978f7a7d05472f7beaa6e5aabb414dea 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -552,8 +552,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories 
 # with spaces.
 
-INPUT                  = mime.py \
-                         Milter
+INPUT                  = mime.py doc/mainpage.py doc/milter.py Milter
 
 # This tag can be used to specify the character encoding of the source files 
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is 
diff --git a/Milter/__init__.py b/Milter/__init__.py
index ab8a3dec6be6586c17796213e4879d5befa72168..960cad10c5375fd7a081548b74de230c40ad042e 100755
--- a/Milter/__init__.py
+++ b/Milter/__init__.py
@@ -4,7 +4,7 @@
 # Clients generally subclass Milter.Base and define callback
 # methods.
 #
-# author Stuart D. Gathman <stuart@bmsi.com>
+# @author Stuart D. Gathman <stuart@bmsi.com>
 # Copyright 2001,2009 Business Management Systems, Inc.
 # This code is under the GNU General Public License.  See COPYING for details.
 
@@ -224,6 +224,7 @@ class Base(object):
   # RCPT TO command (and as delivered to the envrcpt callback), for example
   # "self.addrcpt('<foo@example.com>')".  
   # @param rcpt the message recipient 
+  # @param params an optional list of ESMTP parameters
   def addrcpt(self,rcpt,params=None):
     if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT")
     return self._ctx.addrcpt(rcpt,params)
@@ -242,6 +243,12 @@ class Base(object):
     if not self._actions & MODBODY: raise DisabledAction("MODBODY")
     return self._ctx.replacebody(body)
 
+  ## Change the SMTP envelope sender address.
+  # The syntax of the sender is that same as used in the SMTP
+  # MAIL FROM command (and as delivered to the envfrom callback),
+  # for example <code>self.chgfrom('<bar@example.com>')</code>.
+  # @param sender the new sender address
+  # @param params an optional list of ESMTP parameters
   def chgfrom(self,sender,params=None):
     if not self._actions & CHGFROM: raise DisabledAction("CHGFROM")
     return self._ctx.chgfrom(sender,params)
@@ -325,13 +332,19 @@ class Milter(Base):
     self.log("close")
     return CONTINUE
 
+## The milter connection factory
+# This factory method is called for each connection to create the
+# python object that tracks the connection.  It should return
+# an object derived from Milter.Base.
 factory = Milter
 
+## @private
 def negotiate_callback(ctx,opts):
   m = factory()
   m._setctx(ctx)
   return m.negotiate(opts)
 
+## @private
 def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
   m = ctx.getpriv()
   if not m:     
@@ -341,6 +354,7 @@ def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
     m._setctx(ctx)
   return m.connect(hostname,family,hostaddr)
 
+## @private
 def close_callback(ctx):
   m = ctx.getpriv()
   if not m: return CONTINUE
@@ -350,8 +364,10 @@ def close_callback(ctx):
     m._setctx(None)	# release milterContext
   return rc
 
+## Convert ESMTP parameters with values to a keyword dictionary.
+# @deprecated You probably want Milter.param2dict instead.
 def dictfromlist(args):
-  "Convert ESMTP parm list to keyword dictionary."
+  "Convert ESMTP parms with values to keyword dictionary."
   kw = {}
   for s in args:
     pos = s.find('=')
@@ -359,6 +375,16 @@ def dictfromlist(args):
       kw[s[:pos].upper()] = s[pos+1:]
   return kw
 
+## Convert ESMTP parm list to keyword dictionary.
+# Params with no value are set to None in the dictionary.
+# @param str list of param strings of the form "NAME" or "NAME=VALUE"
+def param2dict(str): 
+  "Convert ESMTP parm list to keyword dictionary."
+  pairs = [x.split('=',1) for x in str]
+  for e in pairs:
+    if len(e) < 2: e.append(None)
+  return dict([(k.upper(),v) for k,v in pairs])
+  
 def envcallback(c,args):
   """Call function c with ESMTP parms converted to keyword parameters.
   Can be used in the envfrom and/or envrcpt callbacks to process
@@ -373,6 +399,22 @@ def envcallback(c,args):
       pargs.append(s)
   return c(*pargs,**kw)
 
+## Run the milter.
+# The MTA can communicate with the milter by means of a
+# unix, inet, or inet6 socket. By default, a unix domain socket
+# is used.  It must not exist,
+# and sendmail will throw warnings if, eg, the file is under a
+# group or world writable directory.
+# <pre>
+# setconn('unix:/var/run/pythonfilter')
+# setconn('inet:8800') 			# listen on ANY interface
+# setconn('inet:7871@@publichost')	# listen on a specific interface
+# setconn('inet6:8020')
+# </pre>
+# @param name the name of the milter known by the MTA
+# @param socketname the descriptor of the unix socket 
+# @param timeout the time in secs the MTA should wait for a response before 
+#	considering this milter dead
 def runmilter(name,socketname,timeout = 0):
   # This bit is here on the assumption that you will be starting this filter
   # before sendmail.  If sendmail is not running and the socket already exists,
diff --git a/doc/mainpage.py b/doc/mainpage.py
new file mode 100644
index 0000000000000000000000000000000000000000..e63f2b28db8874554c1d1fb12708e490e729a0d2
--- /dev/null
+++ b/doc/mainpage.py
@@ -0,0 +1,36 @@
+## @mainpage Writing Milters in Python
+#
+# 
+# At the lowest level, the <code>milter</code> module provides a thin wrapper
+# around the <a href="https://www.milter.org/developers/api/index"> sendmail
+# libmilter API</a>.  This API lets you register callbacks for a number of
+# events in the process of sendmail receiving a message via SMTP.  These
+# events include the initial connection from a MTA, the envelope sender and
+# recipients, the top level mail headers, and the message body.  There are
+# options to mangle all of these components of the message as it passes through
+# the milter.
+# 
+# At the next level, the <code>Milter</code> module (note the case difference)
+# provides a Python friendly object oriented wrapper for the low level API.  To
+# use the Milter module, an application registers a 'factory' to create an
+# object for each connection from a MTA to sendmail.  These connection objects
+# must provide methods corresponding to the libmilter callback events.
+# 
+# Each event method returns a code to tell sendmail whether to proceed with
+# processing the message.  This is a big advantage of milters over other mail
+# filtering systems.  Unwanted mail can be stopped in its tracks at the
+# earliest possible point.
+# 
+# The <code>Milter.Base</code> class provides default implementations for
+# event methods that do nothing, and also provides wrappers for the libmilter
+# methods to mutate the message.  It automatically negotiates with MTA
+# which protocol steps need to be processed by the milter, based on
+# which callback methods are overridden.
+#
+# The <code>Milter.Milter</code> class provides an alternate default
+# implementation that logs the main milter events, but otherwise does nothing.
+# It is provided for compatibility.
+# 
+# The <code>mime</code> module provides a wrapper for the Python email package
+# that fixes some bugs, and simplifies modifying selected parts of a MIME
+# message.
diff --git a/doc/milter.py b/doc/milter.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcd8ee6b0eb83556ebee318e4906ed13c0307c1d
--- /dev/null
+++ b/doc/milter.py
@@ -0,0 +1,44 @@
+# Document miltermodule for Doxygen
+#
+
+## @package milter
+#
+# A thin wrapper around libmilter.
+#
+
+class milterContext(object):
+  def getsymval(self,sym): pass
+  def setreply(self,rcode,xcode,*msg): pass
+  def addheader(self,name,value,idx=-1): pass
+  def chgheader(self,name,idx,value): pass
+  def addrcpt(self,rcpt,params=None): pass
+  def delrcpt(self,rcpt): pass
+  def replacebody(self,data): pass
+  def setpriv(self,priv): pass
+  def getpriv(self): pass
+  def quarantine(self,reason): pass
+  def progress(self): pass
+  def chgfrom(self,sender,param=None): pass
+  def setsmlist(self,stage,macrolist): pass
+
+class error(Exception): pass
+
+def set_flags(flags): pass
+def set_connect_callback(cb): pass
+def set_helo_callback(cb): pass
+def set_envfrom_callback(cb): pass
+def set_envrcpt_callback(cb): pass
+def set_header_callback(cb): pass
+def set_eoh_callback(cb): pass
+def set_body_callback(cb): pass
+def set_abort_callback(cb): pass
+def set_close_callback(cb): pass
+def set_exception_policy(code): pass
+def register(name,negotiate=None,unknown=None,data=None): pass
+def opensocket(rmsock): pass
+def main(): pass
+def setdbg(lev): pass
+def settimeout(secs): pass
+def setbacklog(n): pass
+def setconn(s): pass
+def stop(): pass
diff --git a/mime.py b/mime.py
index 6ceb6bdadf12cc7af80ad835242bbd508323919b..2c8b5304539d86adeb386da3e87db8c12dfcde13 100644
--- a/mime.py
+++ b/mime.py
@@ -1,4 +1,7 @@
 # $Log$
+# Revision 1.5  2005/07/20 14:49:43  customdesigned
+# Handle corrupt and empty ZIP files.
+#
 # Revision 1.4  2005/06/17 01:49:39  customdesigned
 # Handle zip within zip.
 #
@@ -70,8 +73,12 @@
 # with old milter code.
 #
 
-# This module provides a "defang" function to replace naughty attachments
-# with a warning message.
+## @package mime
+# This module provides a "defang" function to replace naughty attachments.
+# 
+# We also provide workarounds for bugs in the email module that comes 
+# with python.  The "bugs" fixed mostly come up only with malformed
+# messages - but that is what you have when dealing with spam.
 
 # Author: Stuart D. Gathman <stuart@bmsi.com>
 # Copyright 2001,2002,2003,2004,2005 Business Management Systems, Inc.
@@ -93,6 +100,8 @@ from email import Errors
 
 from types import ListType,StringType
 
+## Return a list of filenames in a zip file.
+# Embedded zip files are recursively expanded.
 def zipnames(txt):
   fp =  StringIO.StringIO(txt)
   zipf = zipfile.ZipFile(fp,'r')
@@ -103,6 +112,8 @@ def zipnames(txt):
       names += zipnames(zipf.read(nm))
   return names
 
+## Fix multipart handling in email.Generator.
+#
 class MimeGenerator(Generator):
     def _dispatch(self, msg):
         # Get the Content-Type: for the message, then try to dispatch to
@@ -142,11 +153,8 @@ def _unquotevalue(value):
 
 from email.Message import _parseparam
 
-# Enhance email.Message 
-# - Provide a headerchange event for integration with Milter
-#   Headerchange attribute can be assigned a function to be called when
-#   changing headers.  The signature is:
-#	headerchange(msg,name,value) -> None
+## Enhance email.Message 
+#
 # - Track modifications to headers of body or any part independently
 
 class MimeMessage(Message):
@@ -158,6 +166,12 @@ class MimeMessage(Message):
     self.submsg = None
     self.modified = False
 
+  ## @var headerchange
+  # Provide a headerchange event for integration with Milter.
+  #   The headerchange attribute can be assigned a function to be called when
+  #   changing headers.  The signature is:
+  #   headerchange(msg,name,value) -> None
+
   def get_param(self, param, failobj=None, header='content-type', unquote=True):
     val = Message.get_param(self,param,failobj,header,unquote)
     if val != failobj and param == 'boundary' and unquote: