Using zope.sendmail

This package is useful when your Zope 3 application wants to send email. It integrates with the transaction mechanism and queues your emails to be sent on successful commits only.

Installation on Windows/ Buildout

When using Windows, pywin32 is required. pywin32 can only be installed using pip, it does not work with ``zc.buildout``. When using buildout, prior to the execution of buildout, use pip to install pywin32.

API

An application that wants to send an email can do so by getting the appropriate zope.sendmail.interfaces.IMailDelivery utility. The standard library’s email module is useful for formatting the message according to RFC-2822:

import email.MIMEText
import email.Header
from zope.sendmail.interfaces import IMailDelivery
from zope.component import getUtility

def send_email(sender, recipient, subject, body):
    msg = email.MIMEText.MIMEText(body.encode('UTF-8'), 'plain', 'UTF-8')
    msg["From"] = sender
    msg["To"] = recipient
    msg["Subject"] = email.Header.Header(subject, 'UTF-8')
    mailer = getUtility(IMailDelivery, 'my-app.mailer')
    mailer.send(sender, [recipient], msg.as_string())

In real-world code you may need to do extra work to format the ‘From’ and ‘To’ headers correctly, if the addresses contain a real-name part with non-ASCII characters. You can find a recipe for that in this blog post: http://mg.pov.lt/blog/unicode-emails-in-python.html

Configuration

The code above used a named IMailDelivery utility. It is your responsibility to define one, as Zope 3 doesn’t provide one by default. You can define an IMailDelivery utility in your site.zcml with a configuration directive:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:mail="http://namespaces.zope.org/mail"

    <mail:queuedDelivery
        name="my-app.mailer"
        permission="zope.Public"
        mailer="smtp"
        queuePath="var/mailqueue"
        />

</configure>

The mail:queuedDelivery directive stores every email in a queue (a standard Maildir folder on the file system in a given directory) and sends them from a background thread. There’s an alternative directive, mail:directDelivery, that sends them from the same thread. This may slow down transaction commits (especially if the SMTP server is slow to respond) and increase the loading time of web pages.

Mailers

The mailer argument of the mail:queuedDelivery utility chooses the appropriate IMailer utility that will be used to deliver email. There are alternative ways of doing that, for example, SMTP or piping the message to an external program. Currently zope.sendmail supports only plain SMTP. [1]

If the same system that runs your Zope 3 server also has an SMTP server on port 25, you can use the default smtp mailer. If you want to use a different SMTP server, define your own utility like this:

<configure xmlns="http://namespaces.zope.org/zope"
           xmlns:mail="http://namespaces.zope.org/mail"

    <mail:smtpMailer
        name="my-app.smtp"
        hostname="mail.my-app.com"
        port="25"
        />

    <mail:queuedDelivery
        name="my-app.mailer"
        permission="zope.Public"
        mailer="my-app.smtp"
        queuepath="var/mailqueue"
        />

</configure>

Testing

Obviously, you don’t want your automated unit/functional test runs to send real emails. You’ll have to define a fake email delivery utility in your test layer. Something like this will do the trick:

@implements(IMailDelivery)
class FakeMailDelivery(object):

    def send(self, source, dest, body):
        print("*** Sending email from %s to %s:" % (source, dest))
        print(body)
        return 'fake-message-id@example.com'

Register it with the standard utility directive:

<utility name="my-app.mailer" factory="my-app.testing.FakeMailDelivery" />

Problems with zope.sendmail

  • The API is a bit inconvenient to use (e.g. you have to do the message formatting by yourself).

  • The configuration should be done in zope.conf, not in ZCML.