Edit me on GitHub

pyramid_mailer

pyramid_mailer is a package for the Pyramid framework to take the pain out of sending emails. It is compatible with Python 2.5, 2.6, 2.7, and 3.2. It has the following features:

  1. A wrapper around the low-level email functionality of standard Python. This includes handling multipart emails with both text and HTML content, and file attachments.
  2. The option of directly sending an email or adding it to the queue in your maildir.
  3. Wrapping email sending in the transaction manager. If you have a view that sends a customer an email for example, and there is an error in that view (for example, a database error) then this ensures that the email is not sent.
  4. A DummyMailer class to help with writing unit tests, or other situations where you want to avoid emails being sent accidentally from a non-production install.

pyramid_mailer uses the repoze_sendmail package for general email sending, queuing and transaction management, and it borrows code from Zed Shaw’s Lamson library for low-level multipart message encoding and wrapping.

Installation

Install using pip install pyramid_mailer or easy_install pyramid_mailer.

If installing from source, untar/unzip, cd into the directory and do python setup.py install.

The source repository is on Github. Please report any bugs, issues or queries there.

Getting Started (The Easier Way)

Or, in your application’s configuration development.ini add:

pyramid.includes =
   pyramid_mailer
   ...
   pyramid_debugtoolbar
   pyramid_tm

Or, in your application’s configuration stanza use the pyramid.config.Configurator.include() method:

config.include('pyramid_mailer')

Thereafter in view code, use the get_mailer() API to obtain the configured mailer:

from pyramid_mailer import get_mailer
mailer = get_mailer(request)

To send a message, you must first create a Message instance:

from pyramid_mailer.message import Message

message = Message(subject="hello world",
                  sender="admin@mysite.com",
                  recipients=["arthur.dent@gmail.com"],
                  body="hello, arthur")

The Message is then passed to the Mailer instance. You can either send the message right away:

mailer.send(message)

or add it to your mail queue (a maildir on disk):

mailer.send_to_queue(message)

Usually you provide the sender to your Message instance. Often however a site might just use a single from address. If that is the case you can provide the default_sender to your Mailer and this will be used in throughout your application as the default if the sender is not otherwise provided.

If you don’t want to use transactions, you can side-step them by using send_immediately():

mailer.send_immediately(message, fail_silently=False)

This will send the email immediately, without the transaction, so if it fails you have to deal with it manually. The fail_silently flag will swallow any connection errors silently - if it’s not important whether the email gets sent.

Getting Started (The Harder Way)

To get started the harder way (without using config.include), create an instance of pyramid_mailer.mailer.Mailer:

from pyramid_mailer.mailer import Mailer

mailer = Mailer()

The mailer can take a number of optional settings, detailed in Configuration. It’s a good idea to create a single Mailer instance for your application, and add it to your registry in your configuration setup:

config = Configurator(settings=settings)
config.registry['mailer'] = Mailer.from_settings(settings)

or alternatively:

from pyramid_mailer import mailer_factory_from_settings
config.registry['mailer'] = mailer_factory_from_settings(settings)

You can then access your mailer in a view:

def my_view(request):
    mailer = request.registry['mailer']

Note that the pyramid_mailer.get_mailer() API will not work if you construct and set your own mailer in this way.

Configuration

If you configure a Mailer using from_settings() or via config.include('pyramid_mailer'), you can pass the settings from your Paste .ini file. For example:

[app:myproject]
mail.host = localhost
mail.port = 25

By default, the prefix is assumed to be mail.. If you use the config.include mechanism, to set another prefix, use the pyramid_mailer.prefix key in the config file. For example:

[app:myproject]
foo.host = localhost
foo.port = 25
pyramid_mailer.prefix = foo.

If you use the pyramid_mailer.mailer.Mailer.from_settings() or pyramid_mailer.mailer_factory_from_settings() API, these accept a prefix directly; for example:

mailer_factory_from_settings(settings, prefix='foo.')

If you don’t use Paste, just pass the settings directly into your Pyramid Configurator:

settings = {'mail.host':'localhost', 'mail.port':'25'}
Configurator(settings=settings)
config.include('pyramid_mailer')

The available settings are listed below.

Setting Default Description
mail.host localhost SMTP host
mail.port 25 SMTP port
mail.username None SMTP username
mail.password None SMTP password
mail.tls False Use TLS
mail.ssl False Use SSL
mail.keyfile None SSL key file
mail.certfile None SSL certificate file
mail.queue_path None Location of maildir
mail.default_sender None Default from address
mail.debug 0 SMTP debug level
mail.sendmail_app /usr/sbin/sendmail Sendmail executable
mail.sendmail_template {sendmail_app} -t -i -f {sender} Template for sendmail execution
Note: SSL will only work with pyramid_mailer if you are using Python
2.6 or higher, as it uses the SSL additions to the smtplib package. While it may be possible to work around this if you have to use Python 2.5 or lower, pyramid_mailer does not support this out of the box.

Note: the mail.debug option will be passed to the underlying smtplib connection. Any values for this option that Python would consider > 0 will result in debug messages for all messages sent and received from the server. Thus, specifying mail.debug with any value will result in debug messages as pyramid_mailer will not attempt to coerce this value from its original string.

Transactions

If you are using transaction management with your Pyramid application then pyramid_mailer will only send the emails (or add them to the mail queue) when the transactions are committed.

For example:

import transaction

from pyramid_mailer.mailer import Mailer
from pyramid_mailer.message import Message

mailer = Mailer()
message = Message(subject="hello arthur",
                  sender="ford.prefect@gmail.com",
                  recipients=['arthur.dent@gmail.com'],
                  body="hello from ford")


mailer.send(message)
transaction.commit()

The email is not actually sent until the transaction is committed.

When the repoze.tm2 tm middleware is in your Pyramid WSGI pipeline or if you’ve included the pyramid_tm package in your Pyramid configuration, transactions are already managed for you, so you don’t need to explicitly commit or abort within code that sends mail. Instead, if an exception is raised, the transaction will implicitly be aborted and mail will not be sent; otherwise it will be committed, and mail will be sent.

Attachments

Attachments are added using the pyramid_mailer.message.Attachment class:

from pyramid_mailer.message import Attachment
from pyramid_mailer.message import Message

message = Message()

photo_data = open("photo.jpg", "rb").read()
attachment = Attachment("photo.jpg", "image/jpg", photo_data)

message.attach(attachment)

You can pass the data either as a string or file object, so the above code could be rewritten:

from pyramid_mailer.message import Attachment
from pyramid_mailer.message import Message

message = Message()

attachment = Attachment("photo.jpg", "image/jpg",
                        open("photo.jpg", "rb"))

message.attach(attachment)

A transfer encoding can be specified via the transfer_encoding option. Supported options are currently base64 (the default) and quoted-printable.

You can also pass an attachment as the body and/or html arguments to specify Content-Transfer-Encoding or other Attachment attributes:

from pyramid_mailer.message import Attachment
from pyramid_mailer.message import Message

body = Attachment(data="hello, arthur",
                  transfer_encoding="quoted-printable")
html = Attachment(data="<p>hello, arthur</p>",
                  transfer_encoding="quoted-printable")
message = Message(body=body, html=html)

Debugging

If your site is in development and you want to avoid accidental sending of any emails to customers, but still see what emails would get sent, you can use config.include('pyramid_mailer.debug') to make the current mailer an instance of the pyramid_mailer.mailer.DebugMailer, hence writing all emails to a file instead of sending them out. In other words if you add pyramid_mailer.debug to your development.ini, all emails that would be sent out will instead get written to files so you can inspect them:

pyramid.includes =
   pyramid_mailer.debug
   ...
   pyramid_debugtoolbar
   pyramid_tm

Unit tests

When running unit tests you probably don’t want to actually send any emails inadvertently. However it’s still useful to keep track of what emails would be sent in your tests.

In either case, config.include('pyramid_mailer.testing') can be used to make the current mailer an instance of the pyramid_mailer.mailer.DummyMailer:

from pyramid import testing

class TestViews(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
        self.config.include('pyramid_mailer.testing')

    def tearDown(self):
        testing.tearDown()

    def test_some_view(self):
        from pyramid.testing import DummyRequest
        from pyramid_mailer import get_mailer
        request = DummyRequest()
        mailer = get_mailer(request)
        response = some_view(request)

One can also use the DummyMailer to keep track of emails sent from a WebTest functional test.:

class FunctionalTests(unittest.TestCase):
    def setUp(self):
        from myapp import main
        settings = {'pyramid.includes' : 'pyramid_mailer.testing'}
        app = main({}, **settings)
        from webtest import TestApp
        self.testapp = TestApp(app)

    def test_some_functionality(self):
        res = self.testapp.get('/post_email', status=200)
        registry = self.testapp.app.registry
        mailer = get_mailer(registry)

The DummyMailer instance keeps track of emails “sent” in two properties: queue for emails send via pyramid_mailer.mailer.Mailer.send_to_queue() and outbox for emails sent via pyramid_mailer.mailer.Mailer.send(). Each stores the individual Message instances:

self.assertEqual(len(mailer.outbox), 1)
self.assertEqual(mailer.outbox[0].subject, "hello world")

self.assertEqual(len(mailer.queue), 1)
self.assertEqual(mailer.queue[0].subject, "hello world")

Queue

When you send mail to a queue via pyramid_mailer.mailer.Mailer.send_to_queue(), the mail will be placed into a maildir directory specified by the queue_path parameter or setting to pyramid_mailer.mailer.Mailer. A separate process will need to be launched to monitor this maildir and take actions based on its state. Such a program comes as part of repoze_sendmail (a dependency of the pyramid_mailer package). It is known as qp. qp will be installed into your Python (or virtualenv) bin or Scripts directory when you install repoze_sendmail.

qp is a script that is meant to be run as a cron job because what it does is that it looks at maildir and sends messages. You’ll need to arrange for qp to be a long-running process that monitors the maildir state.:

$ bin/qp /path/to/mail/queue

This will attempt to use the localhost SMTP server to send any messages in the queue over time. qp has other options that allow you to choose different settings. Use it’s --help parameter to see more:

$ bin/qp --help

Note

Sending messages via the queue requires the use of a transaction manager. If no manager is enabled, it must be emulated by issuing a manual commit via transaction.commit().

import transaction
tx = transaction.begin()
mailer.send_to_queue(msg)
try:
    tx.commit()
except Exception:
    # handle a failed delivery

API

mailer_factory_from_settings(settings, prefix='mail.')[source]

Factory function to create a Mailer instance from settings. Equivalent to pyramid_mailer.mailer.Mailer.from_settings().

Versionadded:0.2.2
get_mailer(request)[source]

Obtain a mailer previously registered via config.include('pyramid_mailer') or config.include('pyramid_mailer.testing').

Versionadded:0.4
class Mailer(host='localhost', port=25, username=None, password=None, tls=False, ssl=False, keyfile=None, certfile=None, queue_path=None, default_sender=None, sendmail_app=None, sendmail_template=None, debug=0)[source]

Manages sending of email messages.

Parameters:
  • host – SMTP hostname
  • port – SMTP port
  • username – SMTP username
  • password – SMPT password
  • tls – use TLS
  • ssl – use SSL
  • keyfile – SSL key file
  • certfile – SSL certificate file
  • queue_path – path to maildir for queued messages
  • default_sender – default “from” address
  • sendmail_app – path to “sendmail” binary. repoze defaults to “/usr/sbin/sendmail”
  • sendmail_template – custom commandline template for sendmail binary, defaults to’[“{sendmail_app}”, “-t”, “-i”, “-f”, “{sender}”]’
  • debug – SMTP debug level
classmethod from_settings(settings, prefix='mail.')[source]

Create a new instance of ‘Mailer’ from settings dict.

Parameters:
  • settings – a settings dict-like
  • prefix – prefix separating ‘pyramid_mailer’ settings
send(message)[source]

Send a message.

The message is handled inside a transaction, so in case of failure (or the message fails) the message will not be sent.

Parameters:message – a ‘Message’ instance.
send_immediately(message, fail_silently=False)[source]

Send a message immediately, outside the transaction manager.

If there is a connection error to the mail server this will have to be handled manually. However if you pass fail_silently the error will be swallowed.

Versionadded:

0.3

Parameters:
  • message – a ‘Message’ instance.
  • fail_silently – silently handle connection errors.
send_immediately_sendmail(message, fail_silently=False)[source]

Send a message immediately, outside the transaction manager.

Uses the local sendmail option

If there is a connection error to the mail server this will have to be handled manually. However if you pass fail_silently the error will be swallowed.

Parameters:
  • message – a ‘Message’ instance.
  • fail_silently – silently handle connection errors.
send_sendmail(message)[source]

Send a message within the transaction manager.

Uses the local sendmail option

Parameters:message – a ‘Message’ instance.
send_to_queue(message)[source]

Add a message to a maildir queue.

In order to handle this, the setting ‘mail.queue_path’ must be provided and must point to a valid maildir.

Parameters:message – a ‘Message’ instance.
class DummyMailer[source]

Dummy mailer instance, used for example in unit tests.

Sent messages are appended to ‘outbox’ list.

Queued messages are appended to ‘queue’ list.

send(message)[source]

Mock sending a transactional message via SMTP.

The message is appended to the ‘outbox’ list.

Parameters:message – a ‘Message’ instance.
send_immediately(message, fail_silently=False)[source]

Mock sending an immediate (non-transactional) message.

The message is appended to the ‘outbox’ list.

Versionadded:

0.3

Parameters:
  • message – a ‘Message’ instance.
  • fail_silently – swallow connection errors (ignored here)
send_immediately_sendmail(message, fail_silently=False)[source]

Mock sending an immediate (non-transactional) message.

The message is added to the ‘outbox’ list.

Parameters:
  • message – a ‘Message’ instance.
  • fail_silently – swallow connection errors (ignored here)
send_sendmail(message)[source]

Mock sending a transactional message via sendmail.

The message is added to the ‘outbox’ list.

Parameters:message – a ‘Message’ instance.
send_to_queue(message)[source]

Mock sending to a maildir queue.

The message is appended to the ‘queue’ list.

Parameters:message – a ‘Message’ instance.
class Message(subject=None, recipients=None, body=None, html=None, sender=None, cc=None, bcc=None, extra_headers=None, attachments=None)[source]

Encapsulates an email message.

Parameters:
  • subject – email subject header
  • recipients – list of email addresses
  • body – plain text message (may be an Attachment or text)
  • html – HTML message (may be an Attachment or text)
  • sender – email sender address
  • cc – CC list
  • bcc – BCC list
  • extra_headers – dict of extra email headers
  • attachments – list of Attachment instances

The message must have a body or html part (or both) to be successfully sent.

add_bcc(recipient)[source]

Adds an email address to the BCC list.

Parameters:recipient – email address of recipient.
add_cc(recipient)[source]

Adds an email address to the CC list.

Parameters:recipient – email address of recipient.
add_recipient(recipient)[source]

Adds another recipient to the message.

Parameters:recipient – email address of recipient.
attach(attachment)[source]

Adds an attachment to the message.

Parameters:attachment – an Attachment instance.
is_bad_headers()[source]

Checks for bad headers i.e. newlines in subject, sender or recipients.

to_message()[source]

Returns raw email.Message instance. Validates message first.

validate()[source]

Checks if message is valid and raises appropriate exception.

class Attachment(filename=None, content_type=None, data=None, disposition=None, transfer_encoding=None)[source]

Encapsulates file attachment information.

Parameters:
  • filename – filename of attachment (if any)
  • content_type – file mimetype (if any, may contain extra params in the form “text/plain; charset=’utf-8’”).
  • data – the raw file data, either as text or a file object
  • disposition – content-disposition (if any, may contain extra params in the form ‘attachment; filename=”fred.txt”’). If filename is supplied in the disposition, it will be used if no filename is supplied to the Attachment constructor. If disposition is not supplied, it will default to ‘attachment’.
  • transfer_encoding – content-transfer-encoding (if any, may be ‘base64’ or ‘quoted-printable’). If it is not supplied, it will default to ‘base64’.
class InvalidMessage[source]

Raised if message is missing vital headers, such as recipients or sender address.

class BadHeaders[source]

Raised if message contains newlines in headers.