Mailer: Easily Send and Receive Emails in Python

Marrow Mailer

A highly efficient and modular mail delivery framework for Python 2.6+ and 3.2+, formerly called TurboMail.

© 2006-2019, Alice Bevan-McGregor and contributors.

1. What is Marrow Mailer?

Marrow Mailer is a Python library to ease sending emails from your application.

By using Marrow Mailer you can:

  • Easily construct plain text and HTML emails.
  • Improve the testability of your e-mail deliveries.
  • Use different mail delivery management strategies; e.g. immediate, deferred, or even multi-server.
  • Deliver e-mail through a number of alternative transports including SMTP, Amazon SES, sendmail, or even via direct on-disk mbox/maildir.
  • Multiple simultaneous configurations for more targeted delivery.

Mailer supports Python 2.6+ and 3.2+ and there are only light-weight dependencies: marrow.util, marrow.interface, and boto3 if using Amazon SES.

1.1. Goals

Marrow Mailer is all about making email delivery easy. Attempting to utilize the built-in MIME message generation classes can be painful, and interfacing with an SMTP server, or, worse, the command-line sendmail command can make you lose your hair. Mailer handles all of these tasks for you and more.

The most common cases for mail message creation (plain text, html, attachments, and html embeds) are handled by the marrow.mailer.message.Message class. Using this class allows you to write clean, succinct code within your own applications. If you want to use hand-generated MIME messages, or tackle Python’s built-in MIME generation support for an advanced use-case, Mailer allows you to utilize the delivery mechanics without requiring the use of the Message class.

Mailer is not an MTA like Exim, Postfix, sendmail, or qmail. It is designed to deliver your messages to a real mail server (“smart host”) or other back-end which then actually delivers the messages to the recipient’s server. There are a number of true MTAs written in Python, however, including Python’s smtpd, Twisted Mail, pymta, tmda-ofmipd, and Lamson, though this is by no means an exhaustive list.

2. Installation

Installing marrow.mailer is easy, just execute the following in a terminal: 2

pip install marrow.mailer


If you add marrow.mailer to the install_requires argument of the call to setup() in your application’s file, marrow.mailer will be automatically installed and made available when your own application is installed. We recommend using “less than” version numbers to ensure there are no unintentional side-effects when updating. Use "marrow.mailer<4.1" to get all bugfixes for the current release, and "marrow.mailer<5.0" to get bugfixes and feature updates, but ensure that large breaking changes are not installed.

Warning: The 4.0 series is the last to support Python 2.

2.1. Development Version

Development takes place on GitHub in the marrow/mailer project. Issue tracking, documentation, and downloads are provided there.

Installing the current development version requires Git, a distributed source code management system. If you have Git, you can run the following to download and link the development version into your Python runtime:

git clone
(cd mailer; python develop)


You can upgrade to the latest version at any time:

(cd mailer; git pull; python develop)


If you would like to make changes and contribute them back to the project, fork the GitHub project, make your changes, and submit a pull request. This process is beyond the scope of this documentation; for more information, see GitHub’s documentation.

3. Basic Usage

To use Marrow Mailer you instantiate a marrow.mailer.Mailer object with the configuration, then pass Message instances to the Mailer instance’s send() method. This allows you to configure multiple delivery mechanisms and choose, within your code, how you want each message delivered. The configuration is a dictionary of dot-notation keys and their values. Each manager and transport has their own configuration keys.

Configuration keys may utilize a shared, common prefix, such as mail.. By default no prefix is assumed. Manager and transport configurations are each additionally prefixed with manager. and transport., respectively. The following is an example of how to send a message by SMTP:

from marrow.mailer import Mailer, Message

mailer = Mailer(dict(
        transport = dict(
                use = 'smtp',
                host = 'localhost')))

message = Message(author="", to="")
message.subject = "Testing Marrow Mailer"
message.plain = "This is a test."



Another example configuration, using a flat dictionary and delivering to an on-disk maildir mailbox:

    'transport.use': 'maildir',
    '': 'data/maildir'



3.1. Mailer Methods

__init__(config, prefix=None)Create and configure a new Mailer.
start()Start the mailer. Returns the Mailer instance and can thus be chained with construction.
stop()Stop the mailer. This cascades through to the active manager and transports.
send(message)Deliver the given Message instance.
new(author=None, to=None, subject=None, **kw)Create a new bound instance of Message using configured default values.

4. The Message Class

The original format for email messages was defined in RFC 822 which was superseded by RFC 2822. The newest standard document about the format is currently RFC 5322. But the basics of RFC 822 still apply, so for the sake of readability we will just use “RFC 822” to refer to all these RFCs. Please read the official standard documents if this text fails to explain some aspects.

The Marrow Mailer Message class has a large number of attributes and methods, described below.

4.1. Message Methods

__init__(author=None, to=None, subject=None, **kw)Create and populate a new Message. Any attribute may be set by name.
__str__You can easily get the MIME encoded version of the message using the str() built-in.
attach(name, data=None, maintype=None, subtype=None, inline=False)Attach a file (data=None) or string-like. For on-disk files, mimetype will be guessed.
embed(name, data=None)Embed an image from disk or string-like. Only embed images!
send()If the Message instance is bound to a Mailer instance, e.g. having been created by the factory method, deliver the message via that instance.

4.2. Message Attributes

4.2.1. Read/Write Attributes

_idThe message ID, generated for you as needed.
attachmentsA list of MIME-encoded attachments.
authorThe visible author of the message. This maps to the From: header.
toThe visible list of primary intended recipients.
ccA visible list of secondary intended recipients.
bccAn invisible list of tertiary intended recipients.
dateThe visible date/time of the message, defaults to
embeddedA list of MIME-encoded embedded images.
encodingUnicode encoding, defaults to utf-8.
headersA list of additional message headers.
notifyThe address that message disposition notification messages get routed to.
organizationAn extended header for an organization name.
plainPlain text message content. 1
priorityThe X-Priority header.
replyThe address replies should be routed to by default; may differ from author.
retriesThe number of times the message should be retried in the event of a non-critical failure.
richHTML message content. Must have plain text alternative. 1
senderThe designated sender of the message; may differ from author. This is primarily utilized by SMTP delivery.
subjectThe subject of the message.

1 The message bodies may be callables which will be executed when the message is delivered, allowing you to easily utilize templates. Pro tip: to pass arguments to your template, while still allowing for later execution, use functools.partial. When using a threaded manager please be aware of thread-safe issues within your templates.

Any of these attributes can also be defined within your mailer configuration. When you wish to use default values from the configuration you must use the factory method. For example:

mail = Mailer({
        '': 'Example User <>',
        'message.subject': "Test subject."
message =
message.subject = "Test subject."


4.2.2. Read-Only Attributes

idA valid message ID. Regenerated after each delivery.
envelopeThe envelope sender from SMTP terminology. Uses the value of the sender attribute, if set, otherwise the first author address.
mimeThe complete MIME document tree that is the message.
recipientsA combination of to, cc, and bcc address lists.

5. Delivery Managers

5.1. Immediate Manager

The immediate manager attempts to deliver the message using your chosen transport immediately. The request to deliver a message is blocking. There is no configuration for this manager.

5.2. Futures Manager

Futures is a thread pool delivery manager based on the concurrent.futures module introduced in PEP 3148. The use of concurrent.futures and its default thread pool manager allows you to receive notification (via callback or blocking request) of successful delivery and errors.

When you enqueue a message for delivery a Future object is returned to you. For information on what you can do with a Future object, see the relevant section of the Futures PEP.

The Futures manager understands the following configuration directives:

workers1The number of threads to spawn.

The workers configuration directive has the side effect of requiring one transport instance per worker, requiring up to workers simultaneous connections.

5.3. Dynamic Manager

This manager dynamically scales the number of worker threads (and thus simultaneous transport connections) based on the current workload. This is a port of the TurboMail 3 ondemand manager to the Futures API. This manager is somewhat more efficient than the plain Futures manager, and should be the manager in use on production systems.

The Dynamic manager understands the following configuration directives:

workers10The maximum number of threads.
divisor10The number of messages to send before freeing the thread. (A.k.a. “exhaustion”.)
timeout60The number of seconds to wait for additional work before freeing the thread. (A.k.a. “starvation”.)

6. Message Transports

Transports are grouped into three primary categories: disk, network, and meta. Meta transports keep the message within Python or only ‘pretend’ to deliver it. Disk transports save the message to disk in some fashion, and networked transports deliver the message over a network. Configuration is similar between transports within the same category.

6.1. Disk Transports

Disk transports are the easiest to get up and running and allow you to off-load final transport of the message to another process or server. These transports are most useful in a larger deployment, but are also great for testing!

There are currently two on-disk transports included with Marrow Mailer: mbox and maildir.

6.1.1. UNIX Mailbox

There is only one configuration directive for the mbox transport:

fileThe on-disk file to use as the mailbox, must be writeable.

There are several important limitations on this mailbox format; notably the use of whole-file locking when changes are to be made, making this transport useless for high-performance or multi-threaded delivery. For details, see the mbox documentation. To efficiently utilize this transport, it is recommended to use the Futures manager with a single worker thread; this avoids lock contention.

6.1.2. UNIX Mail Directory

The maildir transport offers the benefits of a universal on-disk mail storage format with numerous features and none of the limitations of the mbox format. These added features mandate the need for additional configuration directives.

directoryThe on-disk path to the mail directory.
folderNoneA dot-separated subfolder to deliver mail into. The default is the top-level (inbox) folder.
createFalseCreate the target folder if it does not exist at the time of delivery.
separator"!"Additional meta-information is associated with the mail directory format, usually separated by a colon. Because a colon is not a valid character on many operating systems, Marrow Mailer defaults to the de-facto standard of the ! (bang) character.

6.2. Network Transports

Network transports have Python directly communicate over TCP/IP with an external service.

6.2.1. Simple Mail Transport Protocol (SMTP)

SMTP is, far and away, the most ubiquitous mail delivery protocol in existence.

hostNoneThe host name to connect to.
port25 or 465The port to connect to. The default depends on the tls directive’s value.
usernameNoneThe username to authenticate against. If utilizing authentication, it is recommended to enable TLS/SSL.
passwordNoneThe password to authenticate with.
timeoutNoneNetwork communication timeout.
local_hostnameNoneThe hostname to advertise during HELO/EHLO.
debugFalseIf True all SMTP communication will be printed to STDERR.
tls"optional"One of "required", "optional", and "ssl" or any other value to indicate no SSL/TLS.
certfileNoneAn optional SSL certificate to authenticate SSL communication with.
keyfileNoneThe private key for the optional certfile.
pipelineNoneIf a non-zero positive integer, this represents the number of messages to pipeline across a single SMTP connection. Most servers allow up to 10 messages to be delivered.

6.2.2. Internet Mail Access Protocol (IMAP)

Marrow Mailer, via the imap transport, allows you to dump messages directly into folders on remote servers.

hostNoneThe host name to connect to.
sslFalseEnable or disable SSL communication.
port143 or 993Port to connect to; the default value relies on the ssl directive’s value.
usernameNoneThe username to authenticate against. The note from SMTP applies here, too.
passwordNoneThe password to authenticate with.
folder"INBOX"The default IMAP folder path.

6.3. Meta-Transports

6.3.1. Google AppEngine

The appengine transport translates between Mailer’s Message representation and Google AppEngine’s. Note that GAE’s EmailMessage class is not nearly as feature-complete as Mailer’s. The translation covers the following marrow.mailer.Message attributes:

  • author
  • to
  • cc
  • bcc
  • reply
  • subject
  • plain
  • rich
  • attachments (excluding inline/embedded files)

6.3.1. Python Logging

The log transport implements the use of the standard Python logging module for message delivery. Using this module allows you to emit messages which are filtered and directed through standard logging configuration. There are three logging levels used:

DEBUGThis level is used for informational messages such as startup and shutdown.
INFOThis level communicates information about messages being delivered.
CRITICALThis level is used to deliver the MIME content of the message.

Log entries at the INFO level conform to the following syntax:



There is only one configuration directive:

name“marrow.mailer.transport.log”The name of the logger to use.

6.3.1. Mock (Testing) Transport

The mock testing transport is useful if you are writing a manager. It allows you to test to ensure your manager handles various exceptions correctly.

success1.0The probability of successful delivery, handled after the following conditions.
failure0.0The probability of the TransportFailedException exception being raised.
exhaustion0.0The probability of the TransportExhaustedException exception being raised.

All probabilities are floating point numbers between 0.0 (0% chance) and 1.0 (100% chance).

6.3.1. Sendmail Command

If the server your software is running on is configured to deliver mail via the on-disk sendmail command, you can use the sendmail transport to deliver your mail.

path"/usr/sbin/sendmail"The path to the sendmail executable.

6.3.1. Amazon Simple E-Mail Service (SES)

Deliver your messages via the Amazon Simple E-Mail Service with the amazon transport. While Amazon allows you to utilize SMTP for communication, using the correct API allows you to get much richer information back from delivery upon both success and failure. To utilize this transport you must have the boto3 package installed.

idYour Amazon AWS access key identifier.
keyYour Amazon AWS secret access key.

6.3.1. SendGrid

The sendgrid transport uses the email service provider SendGrid to deliver your transactional and marketing messages. Use your SendGrid username and password (user and key), or supply an API key (only key).

userYour SendGrid username. Don’t include this if you’re using an API key.
keyYour SendGrid password, or a SendGrid account API key.

7. Extending Marrow Mailer

Marrow Mailer can be extended in two ways: new managers (such as thread pool management strategies or process pools) and delivery transports. The API for each is quite simple.

One note is that managers and transports only receive the configuration directives targeted at them; it is not possible to inspect other aspects of configuration.

7.1. Delivery Manager API

Delivery managers are responsible for accepting work from the application programmer and (eventually) handing this work to transports for final outbound delivery from the application.

The following are the methods understood by the duck-typed manager API. All methods are required even if they do nothing.

__init__(config, Transport)Initialization code. Transport is a pre-configured transport factory.
startup()Code to execute after initialization and before messages are accepted.
deliver(message)Handle delivery of the given Message instance.
shutdown()Code to execute during shutdown.

A manager must:

  1. Perform no actions during initialization.
  2. Prepare state within the startup() method call. E.g. prepare a thread or transport pool.
  3. Clean up state within the shutdown() method call. E.g. free a thread or transport pool.
  4. Return a documented object from the deliver() method call, preferably a Future instance for interoperability with the core managers.
  5. Accept multiple messages during the lifetime of the manager instance.
  6. Accept multiple startup()/shutdown() cycles.
  7. Understand and correctly handle exceptions that may be raised by message transports, described in §5.3.

Additionally, a manager must not:

  1. Utilize or alter any form of global scope configuration.

7.2. Message Transport API

A message transport is some method whereby a message is sent to an external consumer. Message transports have limited control over how they are utilized by the use of Marrow Mailer exceptions with specific semantic meanings, as described in §6.3.

The following are the methods understood by the duck-typed transport API. All methods are required even if they do nothing.

__init__(config)Initialization code.
startup()Code to execute after initialization and before messages are accepted.
deliver(message)Handle delivery of the given Message instance.
shutdown()Code to execute during shutdown.

Optionally, a transport may define the following additional attribute:

connectedTrue or False based on the current connection status.

A transport must:

  1. Perform no actions during initialization.
  2. Prepare state within the startup() method call. E.g. opening network connections or files.
  3. Clean up state within the shutdown() method call. E.g. closing network connections or files.
  4. Accept multiple messages during the lifetime of the transport instance.
  5. Accept multiple startup()/shutdown() cycles.
  6. Understand and correctly handle exceptions that may be raised by message transports, described in §6.3.

Additionally, a transport must not:

  1. Utilize or alter any form of global scope configuration.

A transport may:

  1. Return data from the deliver() method; this data will be passed through as the return value of the Mailer.send() call or Future callback response value.

7.3. Exceptions

The following table illustrates the semantic meaning of the various internal (and external) exceptions used by Marrow Mailer, managers, and transports.

DeliveryFailedExceptionExternalThe message stored in args[0] could not be delivered for the reason given in args[1]. (These can be accessed as e.msg and e.reason.)
MailerNotRunningExternalRaised when attempting to deliver messages using a dead interface. (Not started, or already shut down.)
MailConfigurationExceptionExternalRaised to indicate some configuration value was required and missing, out of bounds, or otherwise invalid.
TransportFailedExceptionInternalThe transport has failed to deliver the message due to an internal error; a new instance of the transport should be used to retry.
MessageFailedExceptionInternalThe transport has failed to deliver the message due to a problem with the message itself, and no attempt should be made to retry delivery of this message. The transport may still be re-used, however.
TransportExhaustedExceptionInternalThe transport has successfully delivered the message, but can no longer be used for future message delivery; a new instance should be used on the next request.

8. License

Marrow Mailer has been released under the MIT Open Source license.

8.1. The MIT License

Copyright © 2006-2019 Alice Bevan-McGregor and contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.


1 In order to run the full test suite you need to install pymta and its dependencies.

2 If Pip is not available for you, you can use easy_install instead. We have much love for Pip and Distribute, though.

Download details:

Author: marrow

License: MIT license


Mailer: Easily Send and Receive Emails in Python
1.80 GEEK