[Zope3-checkins] CVS: Zope3/src/zope/app/interfaces - mail.py:1.3.2.1

Viktorija Zaksiene ryzaja@codeworks.lt
Thu, 22 May 2003 04:52:11 -0400


Update of /cvs-repository/Zope3/src/zope/app/interfaces
In directory cvs.zope.org:/tmp/cvs-serv25415/src/zope/app/interfaces

Modified Files:
      Tag: cw-mail-branch
	mail.py 
Log Message:
Marius and Viktorija.
Refactored Mail delivery service. The work is still not finished, so we
commit it to the branch.


=== Zope3/src/zope/app/interfaces/mail.py 1.3 => 1.3.2.1 ===
--- Zope3/src/zope/app/interfaces/mail.py:1.3	Wed May 21 06:52:53 2003
+++ Zope3/src/zope/app/interfaces/mail.py	Thu May 22 04:51:40 2003
@@ -11,9 +11,47 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""MailService Interfaces
+"""Mail service interfaces
 
-Interfaces relevant for the MailService
+Email sending from Zope 3 applications works as follows:
+
+- A Zope 3 application locates a mail service (IMailService) and feeds a
+  message to it.  It gets back a unique message ID so it can keep track of the
+  message by subscribing to IMailEvent events.
+
+- The mail service registers with the transaction system to make sure the
+  message is only sent when the transaction commits successfully.  (Among
+  other things this avoids duplicate messages on ConflictErrors.)
+
+- If the mail service is a IQueuedMailService, it puts the message into a
+  queue (a Maildir mailbox in the file system).  A separate process or thread
+  (IMailQueueProcessor) watches the queue and delivers messages
+  asynchronously.  Since the queue is located in the file system, it survives
+  Zope restarts or crashes and the mail is not lost.  The queue processor
+  can implement batching to keep the server load low.
+
+- If the mail service is a IDirectMailService, it delivers messages
+  synchronously during the transaction commit.  This is not a very good idea,
+  as it makes the user wait.  Note that transaction commits must not fail,
+  but that is not a problem, because mail delivery problems dispatch an
+  event instead of raising an exception.
+
+  XXX and there's the problem -- sending events causes unknown code to be
+  executed during the transaction commit phase.  There should be a way to
+  start a new transaction for event processing after this one is commited.
+
+- An IMailQueueProcessor or IDirectMailService actually delivers the messages
+  by using a mailer (IMailer) component that encapsulates the delivery
+  process.  There are currently two mailers:
+
+    - ISMTPMailer sends all messages to a relay host using SMTP
+
+    - ISendmailMailer sends messages by calling an external process (usually
+      /usr/lib/sendmail on Unix systems).
+
+- If mail delivery succeeds, an IMailSentEvent is dispatched by the mailer.
+  If mail delivery fails, no exceptions are raised, but an IMailErrorEvent is
+  dispatched by the mailer.
 
 $Id$
 """
@@ -26,118 +64,138 @@
 
 
 class IMailService(Interface):
-    """A mail service allows someone to send an email to a group of people.
+    """A mail service allows someone to send an email to a group of people."""
 
-    Note: This interface is purposefully held very simple, so that it is easy
-    to provide a basic mail service implementation.
-    """
+    def send(fromaddr, toaddrs, message):
+        """Sends an email message.
 
-    hostname = zope.schema.TextLine(
-        title=_(u"Hostname"),
-        description=_(u"Name of server to be used as SMTP server."))
+        'fromaddr' is the sender address (byte string),
 
-    port = zope.schema.Int(
-        title=_(u"Port"),
-        description=_(u"Port of SMTP service"),
-        default=25)
+        'toaddrs' is a sequence of recipient addresses (byte strings).
 
-    username = zope.schema.TextLine(
-        title=_(u"Username"),
-        description=_(u"Username used for optional SMTP authentication."))
+        'message' is a byte string that contains both headers and body
+        formatted according to RFC 2822.  If it does not contain a Message-Id
+        header, it will be generated and added automatically.
 
-    password = zope.schema.Password(
-        title=_(u"Password"),
-        description=_(u"Password used for optional SMTP authentication."))
+        Returns the message ID.
 
-    def send(fromaddr, toaddrs, message):
-        """Send a message to the tos (list of email unicode strings) with a
-        sender specified in from (unicode string).
+        You can subscribe to IMailEvent events for notification about problems
+        or successful delivery.
+
+        Messages are actually sent during transaction commit.
         """
 
 
-# XXX: Needs better name: AsyncMailService, MailerMailService, ...
-class IAsyncMailService(IMailService):
-    """This mail service handles mail delivery using so called Mailer objects.
+class IDirectMailService(IMailService):
+    """A mail service that delivers messages synchronously during transaction
+    commit.
 
-    The policies for sending the mail are encoded in the Mailer object.  Also,
-    it is recommended that the mailer is called in a different thread, so that
-    the request is not blocked.
+    Not useful for production use, but simpler to set up and use.
     """
 
-    def createMailer(name):
-        """Create a Mailer object which class was registered under the passed
-        name."""
-
-    def getMailerNames():
-        """Return a list of the names of all registered mailers."""
-
-    def getDefaultMailerName():
-        """Return the name of the default Mailer.  None, means there is no
-        default mailer class defined.
-        """
-
-    def send(fromaddr, toaddrs, message, mailer=None):
-        """This interface extends the send method by an optional mailer
-        attribute.  If the mailer is None, an object from the default mailer
-        class is created and used.
-        """
+    mailer = Attribute("IMailer that is used for message delivery")
 
 
-class IMailer(Interface):
-    """This is a generic Mailer interface.
+class IQueuedMailService(IMailService):
+    """A mail service that puts all messages into a queue in the filesystem.
 
-    Mailers implement mailing policies, such as batching, scheduling and so
-    on.
+    Messages will be delivered asynchronously by a separate component.
     """
 
-    def send(fromaddr, toaddrs, message, hostname, port, username, password):
-        """Send a message.  How and when the mailer is going to send
-        out the mail is purely up to the mailer's policy.
-        """
+    queuePath = zope.schema.TextLine(
+        title=_(u"Queue path"),
+        description=_(u"Pathname of the directory used to queue mail."))
 
 
-class IBatchMailer(IMailer):
-    """The Batch Mailer allows for sending emails in batches, so that the
-    server load will not be too high.
+class IMailQueueProcessor(Interface):
+    """A mail queue processor that delivers queueud messages asynchronously.
     """
 
-    batchSize = zope.schema.Int(
-        title=_(u"Batch Size"),
-        description=_(u"Amount of E-mails sent in one batch."),
-        min=1)
-
-    batchDelay = zope.schema.Int(
-        title=_(u"Batch Delay"),
-        description=_(u"Delay time in milliseconds between batches."),
-        min=100)
+    queuePath = zope.schema.TextLine(
+        title=_(u"Queue Path"),
+        description=_(u"Pathname of the directory used to queue mail."))
+
+    pollingInterval = zope.schema.Int(
+        title=_(u"Polling Interval"),
+        description=_(u"How often the queue is checked for new messages"
+                       " (in milliseconds)"),
+        default=5000)
 
+    mailer = Attribute("IMailer that is used for message delivery")
 
-class IScheduleMailer(IMailer):
-    """This mailer allows you to specify a specific date/time to send the
-    mails.
-    """
 
-    sendAt = zope.schema.Datetime(
-        title=_(u"Send at"),
-        description=_(u"Date/time the message should be send."))
+class IMailer(Interface):
+    """Mailer handles syncrhonous mail delivery."""
+
+    def send(fromaddr, toaddrs, message):
+        """Sends an email message.
+
+        'fromaddr' is the sender address (unicode string),
+
+        'toaddrs' is a sequence of recipient addresses (unicode strings).
+
+        'message' contains both headers and body formatted according to RFC
+        2822.  It should contain at least Date, From, To, and Message-Id
+        headers.
+
+        Messages are sent immediatelly.
+
+        Dispatches an IMailSentEvent on successful delivery, otherwise an
+        IMailErrorEvent.
+        """
+
+
+class ISMTPMailer(IMailer):
+    """A mailer that delivers mail to a relay host via SMTP."""
+
+    hostname = zope.schema.TextLine(
+        title=_(u"Hostname"),
+        description=_(u"Name of server to be used as SMTP server."))
+
+    port = zope.schema.Int(
+        title=_(u"Port"),
+        description=_(u"Port of SMTP service"),
+        default=25)
+
+    username = zope.schema.TextLine(
+        title=_(u"Username"),
+        description=_(u"Username used for optional SMTP authentication."))
+
+    password = zope.schema.Password(
+        title=_(u"Password"),
+        description=_(u"Password used for optional SMTP authentication."))
+
+
+class ISendmailMailer(IMailer):
+    """A mailer that delivers mail by calling an external process."""
+
+    command = zope.schema.BytesLine(
+        title=_(u"Command"),
+        description=_(u"Command used to send email."),
+        default="/usr/lib/sendmail -oem -oi -f %(from)s %(to)s")
 
 
 class IMailEvent(IEvent):
-    """Generic Mailer event that can be sent from the mailer or the mail
-    service.
-    """
+    """Generic mail event."""
 
-    mailer = Attribute("Mailer object that is used to send the mail.")
+    messageId = Attribute("Message id according to RFC 2822")
 
 
 class IMailSentEvent(IMailEvent):
-    """Event that is fired when all the mail in the mailer was sent.
+    """Event that is fired when a message is succesfully sent.
 
-    Note: Subscribing to this event eliminates the need for implementing
-    callback functions for the asynchronous delivery.
+    This does not mean that all the recipients have received it, it only
+    means that the message left this system successfully.  It is possible
+    that a bounce message will arrive later from some remote mail server.
     """
 
 
+class IMailErrorEvent(IMailEvent):
+    """Event that is fired when a message cannot be delivered."""
+
+    errorMessage = Attribute("Error message")
+
+
 
 class IMaildirFactory(Interface):
 
@@ -211,4 +269,3 @@
 
         Calling abort() more than once is allowed.
         """
-