A light-weight, modular, message representation and mail delivery framework for Python.

Overview

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.

https://github.com/marrow/mailer

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 boto 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 setup.py 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 https://github.com/marrow/mailer.git
(cd mailer; python setup.py develop)

You can upgrade to the latest version at any time:

(cd mailer; git pull; python setup.py 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')))
mailer.start()

message = Message(author="[email protected]", to="[email protected]")
message.subject = "Testing Marrow Mailer"
message.plain = "This is a test."
mailer.send(message)

mailer.stop()

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

{
    'transport.use': 'maildir',
    'transport.directory': 'data/maildir'
}

3.1. Mailer Methods

Method Description
__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

Method Description
__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 Mailer.new() factory method, deliver the message via that instance.

4.2. Message Attributes

4.2.1. Read/Write Attributes

Attribute Description
_id The message ID, generated for you as needed.
attachments A list of MIME-encoded attachments.
author The visible author of the message. This maps to the From: header.
to The visible list of primary intended recipients.
cc A visible list of secondary intended recipients.
bcc An invisible list of tertiary intended recipients.
date The visible date/time of the message, defaults to datetime.now()
embedded A list of MIME-encoded embedded images.
encoding Unicode encoding, defaults to utf-8.
headers A list of additional message headers.
notify The address that message disposition notification messages get routed to.
organization An extended header for an organization name.
plain Plain text message content. 1
priority The X-Priority header.
reply The address replies should be routed to by default; may differ from author.
retries The number of times the message should be retried in the event of a non-critical failure.
rich HTML message content. Must have plain text alternative. 1
sender The designated sender of the message; may differ from author. This is primarily utilized by SMTP delivery.
subject The 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 Mailer.new() factory method. For example:

mail = Mailer({
        'message.author': 'Example User <[email protected]>',
        'message.subject': "Test subject."
    })
message = mail.new()
message.subject = "Test subject."
message.send()

4.2.2. Read-Only Attributes

Attribute Description
id A valid message ID. Regenerated after each delivery.
envelope The envelope sender from SMTP terminology. Uses the value of the sender attribute, if set, otherwise the first author address.
mime The complete MIME document tree that is the message.
recipients A 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:

Directive Default Description
workers 1 The 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:

Directive Default Description
workers 10 The maximum number of threads.
divisor 10 The number of messages to send before freeing the thread. (A.k.a. “exhaustion”.)
timeout 60 The 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:

Directive Default Description
file The 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.

Directive Default Description
directory The on-disk path to the mail directory.
folder None A dot-separated subfolder to deliver mail into. The default is the top-level (inbox) folder.
create False Create 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.

Directive Default Description
host None The host name to connect to.
port 25 or 465 The port to connect to. The default depends on the tls directive’s value.
username None The username to authenticate against. If utilizing authentication, it is recommended to enable TLS/SSL.
password None The password to authenticate with.
timeout None Network communication timeout.
local_hostname None The hostname to advertise during HELO/EHLO.
debug False If 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.
certfile None An optional SSL certificate to authenticate SSL communication with.
keyfile None The private key for the optional certfile.
pipeline None If 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.

Directive Default Description
host None The host name to connect to.
ssl False Enable or disable SSL communication.
port 143 or 993 Port to connect to; the default value relies on the ssl directive’s value.
username None The username to authenticate against. The note from SMTP applies here, too.
password None The 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:

Level Meaning
DEBUG This level is used for informational messages such as startup and shutdown.
INFO This level communicates information about messages being delivered.
CRITICAL This level is used to deliver the MIME content of the message.

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

DELIVER {ID} {ISODATE} {SIZE} {AUTHOR} {RECIPIENTS}

There is only one configuration directive:

Directive Default Description
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.

Directive Default Description
success 1.0 The probability of successful delivery, handled after the following conditions.
failure 0.0 The probability of the TransportFailedException exception being raised.
exhaustion 0.0 The 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.

Directive Default Description
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 boto package installed.

Directive Default Description
id Your Amazon AWS access key identifier.
key Your 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).

Directive Default Description
user Your SendGrid username. Don’t include this if you’re using an API key.
key Your 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.

Method Description
__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.

Method Description
__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:

connected True 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.

Exception Role Description
DeliveryFailedException External The 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.)
MailerNotRunning External Raised when attempting to deliver messages using a dead interface. (Not started, or already shut down.)
MailConfigurationException External Raised to indicate some configuration value was required and missing, out of bounds, or otherwise invalid.
TransportFailedException Internal The transport has failed to deliver the message due to an internal error; a new instance of the transport should be used to retry.
MessageFailedException Internal The 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.
TransportExhaustedException Internal The 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.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 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.

Comments
  • Broken in 3.8 due to bad import from marrow/cgi

    Broken in 3.8 due to bad import from marrow/cgi

    Looks like it's broken on python 3.8.1 currently.

    Installs fine:

    ❯ pip install marrow.mailer --user                                                                                                         
    Collecting marrow.mailer
      Using cached https://files.pythonhosted.org/packages/8c/37/961103b20bad1a8af86fa67a53748c2bf26e9e242a79fa89dc762d374ea6/marrow.mailer-4.0.3-py2.py3-none-any.whl
    Collecting marrow.util<2.0
      Using cached https://files.pythonhosted.org/packages/d2/19/630a0984ba6d5dc4432ed45d2c9ab8752542fa6ba1590a0e72d1fb742c2e/marrow.util-1.2.3.tar.gz
    Installing collected packages: marrow.util, marrow.mailer
        Running setup.py install for marrow.util ... done
    Successfully installed marrow.mailer-4.0.3 marrow.util-1.2.3
    

    Running a script with the example code:

    from marrow.mailer import Mailer, Message
    
    mailer = Mailer(dict(
            transport = dict(
                    use = 'smtp',
                    host = 'localhost')))
    mailer.start()
    
    message = Message(author="[email protected]", to="[email protected]")
    message.subject = "Testing Marrow Mailer"
    message.plain = "This is a test."
    mailer.send(message)
    
    mailer.stop()
    
    Traceback (most recent call last):
      File "/home/liam/projects/soc_siem_bi_integration/soc_scripts/cleanup.py", line 8, in <module>
        from marrow.mailer import Mailer, Message
      File "/home/liam/.local/lib/python3.8/site-packages/marrow/mailer/__init__.py", line 12, in <module>
        from marrow.mailer.message import Message
      File "/home/liam/.local/lib/python3.8/site-packages/marrow/mailer/message.py", line 21, in <module>
        from marrow.mailer.address import Address, AddressList, AutoConverter
      File "/home/liam/.local/lib/python3.8/site-packages/marrow/mailer/address.py", line 12, in <module>
        from marrow.util.compat import basestring, unicode, unicodestr, native
      File "/home/liam/.local/lib/python3.8/site-packages/marrow/util/compat.py", line 33, in <module>
        from cgi import parse_qsl
    ImportError: cannot import name 'parse_qsl' from 'cgi' (/usr/lib/python3.8/cgi.py)
    

    If I go diving into compat.py and change from cgi import parse_qsl to from urllib.parse import parse_qsl, it works fine. This is makes sense, given Python 3.8 notes:

    parse_qs, parse_qsl, and escape are removed from the cgi module. They are deprecated in Python 3.2 or older. They should be imported from the urllib.parse and html modules instead.
    

    I don't know the ramifications of using urllib.parse over html, though.

    Util is archived, otherwise I would have raised the issue there /shrug.

    1.bug area:packaging org:external 
    opened by iJebus 26
  • Attach files with filenames that include non-ASCII characters

    Attach files with filenames that include non-ASCII characters

    RFC 2231 allows us to specify a language and a charset for filenames that include non-ASCII characters. Python's email module already includes some nifty functionality to handle this. This PR simply exposes that functionality to users of marrow/mailer.

    2.enhancement area:message 
    opened by stevenbuccini 12
  • Automatically populate the Message-Id header.

    Automatically populate the Message-Id header.

    Sending mails with marrow.mailer results in mails without message ids (MID) for my own mailserver and a big one I tested against. (while a third mail server adds its own message id)

    The (valid) message id I try to set via message._id doesn't get sent.

    Mails without a message id get very much higher spam ratings and is against the standard. I see the code referring to generating a message id if there is none set (which I assume to be default) though, so I am unsure why no message id is received.

    2.enhancement area:message 
    opened by sh4d0v1 6
  • Traceback from configuration handler

    Traceback from configuration handler

    Trying to use following configuration:

    from marrow.mailer import Mailer
    mailer = Mailer({'transport': {'use': 'mbox', 'file': '/tmp/komasu.mbox'}})
    

    Gives me following traceback:

    Traceback (most recent call last):
      File "x.py", line 2, in <module>
        mailer = Mailer({'transport': {'use': 'mbox', 'file': '/tmp/komasu.mbox'}})
      File "/home/plaes/.virtualenvs/komasu/lib/python2.7/site-packages/marrow/mailer/__init__.py", line 50, in __init__
        self.manager_config = manager_config = Bunch.partial('manager', config)
      File "/home/plaes/.virtualenvs/komasu/lib/python2.7/site-packages/marrow/util/bunch.py", line 48, in partial
        raise ValueError()
    ValueError
    

    When I add the missing manager: {} to the configuration, it complains about missing message

    opened by plaes 5
  • Attachments should be encoded with base64

    Attachments should be encoded with base64

    Seems like agronholm looked at some emails I sent him with PDF attachments.

    He said that attachments should be encoded with base64

    So it seems as if this is the case and I was able to resolve it see here.

    https://gist.github.com/dfee96dbd57bdeb3498f

    1.bug area:message 
    opened by fork- 5
  • Any maintained fork

    Any maintained fork

    As we have noticed, marrow mailer doesn't work on Python 3.8 and there is no development going on. Is there any maintained fork out there? Or is there any alternative we should be migrating our apps to

    meta:rejected 3.question 
    opened by iamareebjamal 4
  • Document SendGrid transport and support API keys

    Document SendGrid transport and support API keys

    The SendGrid transport works, but doesn't support API keys. This adds the transport to the README, and adds transparent support for API keys. Supply a username and password as user and key, or just an API key as key.

    2.enhancement transport:sendgrid 
    opened by vitorio 4
  • Check Python 3 compatibility.

    Check Python 3 compatibility.

    Issues with SMTPlib and str vs. bytes header values; specifically those of the address values such as From.

    Needs more research on Python 3's smtp library. On the following MIME message:

    Content-Type: text/plain; charset="utf-8"
    MIME-Version: 1.0
    Content-Transfer-Encoding: quoted-printable
    From: =?utf-8?q?Alice_Bevan-McGregor?= <[email protected]>
    Subject: =?utf-8?q?This_is_a_test_message=2E?=
    Date: =?utf-8?q?Wed=2C_23_Nov_2011_00=3A27=3A51_-0500?=
    To: =?utf-8?q?Your_Name_Here?= <[email protected]>
    X-Mailer: =?utf-8?q?marrow=2Emailer_4=2E0=2E0b3?=
    
    Testing!
    

    I'm getting:

    ERROR:marrow.mailer.transport.smtp:<[email protected]> EXCEPTION TypeError
    Traceback (most recent call last):
      File "/Users/amcgregor/Projects/Marrow/src/marrow.mailer/marrow/mailer/transport/smtp.py", line 119, in send_with_smtp
        self.connection.sendmail(sender, recipients, content)
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/smtplib.py", line 741, in sendmail
        (code, resp) = self.mail(from_addr, esmtp_opts)
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/smtplib.py", line 482, in mail
        self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/smtplib.py", line 143, in quoteaddr
        m = email.utils.parseaddr(addr)[1]
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/email/utils.py", line 192, in parseaddr
        addrs = _AddressList(addr).addresslist
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/email/_parseaddr.py", line 471, in __init__
        self.addresslist = self.getaddrlist()
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/email/_parseaddr.py", line 224, in getaddrlist
        ad = self.getaddress()
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/email/_parseaddr.py", line 234, in getaddress
        self.gotonext()
      File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/email/_parseaddr.py", line 207, in gotonext
        if self.field[self.pos] in self.LWS + '\n\r':
    TypeError: 'in <string>' requires string as left operand, not int
    
    2.enhancement area:message transport:smtp 
    opened by amcgregor 4
  • Next fixes

    Next fixes

    Hey Alice, I finally managed to get a deeper go at the next branch (on Python 3.9, to which I just upgraded).

    I came across a few errors right away for which I have provided what I think should be fixes (unless I misinterpreted your intention behind the code pieces in question).

    Let me know what you think. :)

    opened by homeworkprod 3
  • python 3.7/3.8 compatibilty fix: need to pass host and port at init time to the SMTP session object

    python 3.7/3.8 compatibilty fix: need to pass host and port at init time to the SMTP session object

    The new version of smtplib requires an hostname at init time. Also if the mail provider uses a non-standard port (as an example, iCloud uses port 587) and the port is not passed at init time, marrow.mailer will fail with a "connection refused" error (errno 61)

    meta:rejected transport:smtp 
    opened by digiturtle 3
  • Fix Amazon SES issues

    Fix Amazon SES issues

    Using mailer 4.0.1 with Python 2.7 and boto 2.38, the amazon transport doesn't work out of the box.

    • The transport file is called ses.py but the transport is amazon.
    • boto doesn't know what to do with the host argument in the README, and throws this error: TypeError: __init__() got an unexpected keyword argument 'host'
    • boto doesn't know what to do with marrow mailer's use argument, and throws this error: TypeError: __init__() got an unexpected keyword argument 'use'

    This pull request resolves these issues by:

    • README.textile now specifies that the transport is called amazon, addressing #57 (although not providing an ses alias).
    • The host line is removed from README.textile.
    • The use argument is popped before any other parameters are passed into config.

    With these changes I can successfully send mail.

    1.bug 2.enhancement transport:ses 
    opened by vitorio 3
  • Dynamic manager does not work with python3 concurrent futures package.

    Dynamic manager does not work with python3 concurrent futures package.

    Exception

    Traceback (most recent call last):
      File "django/core/handlers/exception.py", line 41, in inner
        response = get_response(request)
      File "django/core/handlers/base.py", line 187, in _get_response
        response = self.process_exception_by_middleware(e, request)
      File "django/core/handlers/base.py", line 185, in _get_response
        response = wrapped_callback(request, *callback_args, **callback_kwargs)
      File "contextlib.py", line 74, in inner
        return func(*args, **kwds)
      File "django/views/decorators/csrf.py", line 58, in wrapped_view
        return view_func(*args, **kwargs)
      File "rest_framework/viewsets.py", line 114, in view
        return self.dispatch(request, *args, **kwargs)
      File "rest_framework/views.py", line 505, in dispatch
        response = self.handle_exception(exc)
      File "rest_framework/views.py", line 465, in handle_exception
        self.raise_uncaught_exception(exc)
      File "rest_framework/views.py", line 476, in raise_uncaught_exception
        raise exc
      File "rest_framework/views.py", line 502, in dispatch
        response = handler(request, *args, **kwargs)
      File "marrow/mailer/__init__.py", line 149, in send
        result = self.manager.deliver(message)
      File "marrow/mailer/manager/dynamic.py", line 177, in deliver
        return self.executor.submit(partial(worker, self.transport), message)
      File "concurrent/futures/thread.py", line 147, in submit
        if self._broken:
    AttributeError: 'ScalingPoolExecutor' object has no attribute '_broken'
    

    Workaround

    from concurrent.futures import ThreadPoolExecutor
    from marrow.mailer.manager.dynamic import DynamicManager, ScalingPoolExecutor
    
    class ScalingPoolExecutorHotFix(ScalingPoolExecutor):
        def __init__(self, workers, divisor, timeout):
            super().__init__(workers, divisor, timeout)
            ThreadPoolExecutor.__init__(self)
    
    class DynamicManagerHotFix(DynamicManager):
        Executor = ScalingPoolExecutorHotFix
    
    Mailer({'manager.use': DynamicManagerHotFix})
    
    opened by proofit404 0
  • SMTP regression and failure during TLS negotiation under Python 3.7.

    SMTP regression and failure during TLS negotiation under Python 3.7.

    Upstream tracking bug: https://bugs.python.org/issue36094


    from marrow.mailer import Mailer, Message
    
    mailer = Mailer(dict(
            transport = dict(
                    use = 'smtp',
                    host = 'smtp.office365.com',
                    port = 587,
                    tls='required',
                    username='[email protected]',
                    password='password')))
    print('connection...')
    mailer.start()
    print('connected')
    
    message = Message(author="[email protected]", to="[email protected]")
    message.subject = "Testing Marrow Mailer"
    message.plain = "This is a test."
    print('send mail')
    mailer.send(message)
    
    print('disconnect')
    mailer.stop()
    

    This works great with python 2.7, but with python 3.7 (tested on 3.7.1 and 3.7.4), I have this error :

    Delivery of message <[email protected]> failed.
    Traceback (most recent call last):
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\manager\util.py", line 50, in __enter__
        transport = pool.transports.get(False)
      File "C:\usr\Python37\lib\queue.py", line 167, in get
        raise Empty
    _queue.Empty
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "minimal_test.py", line 32, in <module>
        mailer.send(message)
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\__init__.py", line 149, in send
        result = self.manager.deliver(message)
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\manager\immediate.py", line 41, in deliver
        with self.transport() as transport:
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\manager\util.py", line 57, in __enter__
        transport.startup()
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\transport\smtp.py", line 50, in startup
        self.connect_to_server()
      File "C:\usr\Python37\lib\site-packages\marrow\mailer\transport\smtp.py", line 86, in connect_to_server
        connection.starttls(self.keyfile, self.certfile)
      File "C:\usr\Python37\lib\smtplib.py", line 771, in starttls
        server_hostname=self._host)
      File "C:\usr\Python37\lib\ssl.py", line 412, in wrap_socket
        session=session
      File "C:\usr\Python37\lib\ssl.py", line 846, in _create
        owner=self, session=self._session,
    ValueError: server_hostname cannot be an empty string or start with a leading dot.
    
    1.bug meta:blocked transport:smtp 
    opened by mentat51 8
  • Documentation for logging and error checking is amiss

    Documentation for logging and error checking is amiss

    hello,

    The documentation for this library does not show any way to enable logging.

    Currently, email is not being delivered and I have no idea why - the ability to enable logging would be of great use.

    Also the docs do not indicate the return value of methods, so I have no idea how to see if each method call is working.

    My usage of this module is very minimal. All of it is shown here

    opened by metaperl 2
  • Comma in Email Address Name

    Comma in Email Address Name

    Commas in the name in an email address cause the address parser to parse the address as multiple addresses and therefore fail.

    Take the following address for example:
    "Bond, James" <[email protected]> It should have Bond, James as the name and [email protected] as the email, but instead it tries to parse Bond as an email address and fails.

    Here's a traceback:

    Traceback (most recent call last):
      File "__main__.py", line 136, in <module>
        main()
      File "__main__.py", line 126, in main
        download_calendars()
      File "__main__.py", line 111, in download_calendars
        sender='[email protected]'
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/message.py", line 79, in __init__
        self.author = author
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/message.py", line 95, in __setattr__
        object.__setattr__(self, name, value)
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/address.py", line 222, in __set__
        value = self.cls(value)
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/address.py", line 145, in __init__
        self.extend(addresses)
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/address.py", line 182, in extend
        values = [Address(val) if not isinstance(val, Address) else val for val in sequence]
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/address.py", line 182, in <listcomp>
        values = [Address(val) if not isinstance(val, Address) else val for val in sequence]
      File "/usr/local/lib/python3.6/site-packages/marrow/mailer/address.py", line 58, in __init__
        raise ValueError('"{0}" is not a valid e-mail address: {1}'.format(email, err))
    ValueError: "Bond" is not a valid e-mail address: An email address must contain a single @
    

    This behavior is only expected if the name is not encapsulated with quotes.

    opened by nashley 0
  • Sendmail transport fails with AttributeError: 'Message' object has no attribute 'sendmail_f'

    Sendmail transport fails with AttributeError: 'Message' object has no attribute 'sendmail_f'

    See traceback below. I was able to get it to work using hasattr to gate that if statement. try/except could also be used. Maybe a better solution elsewhere tho. Could not find any other refs to sendmail_f. I presume y'all were thinking of the "envelop sender" option? Let me know if u want a PR with my patch...

    Shutting down transport due to unhandled exception. Traceback (most recent call last): File "/usr/local/lib/python3.4/dist-packages/marrow/mailer/manager/futures.py", line 28, in worker result = transport.deliver(message) File "/usr/local/lib/python3.4/dist-packages/marrow/mailer/transport/sendmail.py", line 29, in deliver if message.sendmail_f: AttributeError: 'Message' object has no attribute 'sendmail_f'

    opened by pbrackin 1
  • SMTP backend not raising SMTPRecipientsRefused

    SMTP backend not raising SMTPRecipientsRefused

    I am trying to send a message with wrong configured sender. I am getting the following line in the log:

    2017-02-15 03:28:25,154 WARNI [marrow.mailer.transport.smtp] <[email protected]> REFUSED SMTPRecipientsRefused {u'[email protected]': (550, '5.1.0 <[email protected]>: Sender address rejected: User unknown in relay recipient table')}
    

    My problem is that nothing else happens. That exception is not raised, just silently logged.

    I believe an exception like this should be treated similarly to if a sending couldn't happen for example because of wrong password or something. That one raises an exception, this one does not.

    The reason is this line is just silently "eating" all exceptions: https://github.com/marrow/mailer/blob/3995ef98a3f7feb75f1aeb652e6afe40a5c94def/marrow/mailer/transport/smtp.py#L105

    opened by hyperknot 0
Releases(4.0.3)
Owner
Marrow Open Source Collective
A non-profit collective of open source developers working on exciting projects for the public good.
Marrow Open Source Collective
A light-weight, modular, message representation and mail delivery framework for 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-McG

Marrow Open Source Collective 255 Dec 28, 2022
Fast Anonymous Email Sending Tool

Email-Fake Fast Anonymous Email Sending Tool 🏆 Github Statistics : Termux For Install: pkg install python pkg install python2 git clone https://githu

Aryan 7 May 28, 2022
EmailAll - a powerful Email Collect tool

EmailAll A powerful Email Collect tool 0x1 介绍 😲 EmailAll is a powerful Email Co

473 Dec 22, 2022
A SMTP server for use as a pytest fixture that implements encryption and authentication.

SMTPDFix: Test email, locally A simple SMTP server based on aiosmtpd for use as a fixture with pytest that supports encryption and authentication. All

James Warne 11 Sep 03, 2022
Yahoo Mail Validator For Python

Validator Validator helps to know if the mail is valid or not Installation Install The libraries pip install requests bs4 colorama Usage Create a new

Mr Python 3 Mar 12, 2022
Tempmail API aswell as a SMTP server.

Tempmail API/Server Tempmail API aswell as a SMTP server. Website · Report Bug · Request Feature Setup Firstly create a mongodb account, and proceed t

femboy.party 16 Mar 09, 2022
A simple free API that allows you to extract abuse emails from IPs.

Abuse-Email-API A simple free API that allows you to extract abuse emails from IPs. also isnt worth 500 dollars :) Requirements A Debian based OS The

Keratin 1 Dec 20, 2021
Python IMAP for Human beings

Imbox - Python IMAP for Humans Python library for reading IMAP mailboxes and converting email content to machine readable data Requirements Python (3.

Martin Rusev 1.1k Dec 30, 2022
Secret Service Email Encryption/Steganography

SecretService Decoy Encrypted Emailer

Unit 221B 6 Aug 05, 2022
Automatically Send Custom Named Certificates via Mail

Welcome to Certificate Launchpad 🚀 Automatically Send Custom Named Certificates via Email Intro After any event, sending certificates to attendees or

Dc7 16 Oct 16, 2022
Suplantar mails de empresas como google, facebook, github, etc...

Suplantar mails de empresas como google, facebook, github, etc...

piter 3 Feb 05, 2022
SMTP In some vulnerable configurations, email servers can also be aggregated Use information that gives us information about the host or network Give

SMTP In some vulnerable configurations, email servers can also be aggregated Use information that gives us information about the host or network Give. The SMTP protocol supports some basic commands s

m3hr44n 1 Jan 16, 2022
Simple Email Sender using Python 3.

Email Sender 使用 Python 3 实现的简单邮件发送工具。 Version: 0.1.2 (Beta) 主要功能 使用 SMTP 协议发送邮件 支持 SSL/TLS 、 STARTTLS 加密(为保证安全,强制加密发送) 支持邮件模板与邮件生成 支持向多人群发邮件 日志记录 脚本执行

SUMSC 1 Feb 13, 2022
Python library for sending emails.

Mail.py Python library for sending emails. Installation git clone https://github.com/SunPodder/Mail.py cd Mail.py python setup.py install Usage Imp

Sun 4 Nov 24, 2021
xxnx its a simple smtp tool for mails spaming

xxnx its a simple smtp tool for mails spaming what is smpt? Simple Mail Transfer Protocol or smtp service. The Simple Mail Transfer Protocol (SMTP) is

0xD4$H 3 Feb 27, 2022
Envia-emails - A Python Program that creates emails

Envia-emails Os emails é algo muito importante e usado. Pensando nisso, eu criei

José Rodolfo 2 Mar 05, 2022
Euserv_extend captcha solver + pin code(Gmail)

Euserv_extend captcha solver + pin code(Gmail)

19 Nov 30, 2022
A research into mail services used by different business sectors.

A research into mail services used by different business sectors. Data, scripts and results available.

Focus Chen 1 Dec 24, 2021
Send Multiple Mail From List With Python

Send Multiple Mail From List With Python You can send multiple e-mail using HTML themes with Python. Here is the e-mail information to be sent. #The m

Mücahid Eker 1 Dec 23, 2021
Mail-Checker is a python script that lets you see your mails directly from the terminal without having to login each time.

Mail-Checker ##Mail-Checker is a python script that lets you see your mails directly from the terminal without having to login each time. ##Before you

Siddharth Pradeep 1 Jan 12, 2022