An open API service indexing awesome lists of open source software.

https://github.com/jwodder/mailbits

Assorted e-mail utility functions
https://github.com/jwodder/mailbits

available-on-pypi content-type e-mail e-mail-address e-mail-comparison e-mail-inspection email emailmessage python recipients

Last synced: 3 months ago
JSON representation

Assorted e-mail utility functions

Awesome Lists containing this project

README

        

|repostatus| |ci-status| |coverage| |pyversions| |license|

.. |repostatus| image:: https://www.repostatus.org/badges/latest/active.svg
:target: https://www.repostatus.org/#active
:alt: Project Status: Active — The project has reached a stable, usable
state and is being actively developed.

.. |ci-status| image:: https://github.com/jwodder/mailbits/actions/workflows/test.yml/badge.svg
:target: https://github.com/jwodder/mailbits/actions/workflows/test.yml
:alt: CI Status

.. |coverage| image:: https://codecov.io/gh/jwodder/mailbits/branch/master/graph/badge.svg
:target: https://codecov.io/gh/jwodder/mailbits

.. |pyversions| image:: https://img.shields.io/pypi/pyversions/mailbits.svg
:target: https://pypi.org/project/mailbits/

.. |license| image:: https://img.shields.io/github/license/jwodder/mailbits.svg
:target: https://opensource.org/licenses/MIT
:alt: MIT License

`GitHub `_
| `PyPI `_
| `Issues `_
| `Changelog `_

``mailbits`` provides a small assortment of functions for working with the
Python standard library's ``Message``/``EmailMessage``, ``Address``, and
``Group`` types, as well as a couple other features. It can parse & reassemble
Content-Type strings, convert instances of the old ``Message`` class to the new
``EmailMessage``, convert ``Message`` & ``EmailMessage`` instances into
structured ``dict``\s, parse addresses, format address lists, and extract
recipients' raw e-mail addresses from an ``EmailMessage``.

Installation
============
``mailbits`` requires Python 3.8 or higher. Just use `pip
`_ for Python 3 (You have pip, right?) to install it::

python3 -m pip install mailbits

API
===

``ContentType``
---------------

The ``ContentType`` class provides a representation of a parsed Content-Type
header value. Parse Content-Type strings with the ``parse()`` classmethod,
inspect the parts via the ``content_type``, ``maintype``, ``subtype``, and
``params`` attributes (the last three of which can be mutated), convert back to
a string with ``str()``, and convert to ASCII bytes using encoded words for
non-ASCII with ``bytes()``.

>>> from mailbits import ContentType
>>> ct = ContentType.parse("text/plain; charset=utf-8; name*=utf-8''r%C3%A9sum%C3%A9.txt")
>>> ct
ContentType(maintype='text', subtype='plain', params={'charset': 'utf-8', 'name': 'résumé.txt'})
>>> ct.content_type
'text/plain'
>>> ct.maintype
'text'
>>> ct.subtype
'plain'
>>> ct.params
{'charset': 'utf-8', 'name': 'résumé.txt'}
>>> str(ct)
'text/plain; charset="utf-8"; name="résumé.txt"'
>>> bytes(ct)
b'text/plain; charset="utf-8"; name*=utf-8\'\'r%C3%A9sum%C3%A9.txt'

``email2dict()``
----------------

.. code:: python

class MessageDict(TypedDict):
unixfrom: Optional[str]
headers: Dict[str, Any]
preamble: Optional[str]
content: Any
epilogue: Optional[str]

mailbits.email2dict(msg: email.message.Message, include_all: bool = False) -> MessageDict

Convert a ``Message`` object to a ``dict``. All encoded text & bytes are
decoded into their natural values.

Need to examine a ``Message`` but find the builtin Python API too fiddly? Need
to check that a ``Message`` has the content & structure you expect? Need to
compare two ``Message`` instances for equality? Need to pretty-print the
structure of a ``Message``? Then ``email2dict()`` has your back.

By default, any information specific to how the message is encoded (Content-Type
parameters, Content-Transfer-Encoding, etc.) is not reported, as the focus is
on the actual content rather than the choices made in representing it. To
include this information anyway, set ``include_all`` to ``True``.

The output structure has the following fields:

``unixfrom``
The "From " line marking the start of the message in a mbox, if any

``headers``
A ``dict`` mapping lowercased header field names to values. The following
headers have special representations:

``subject``
A single string

``from``, ``to``, ``cc``, ``bcc``, ``resent-from``, ``resent-to``, ``resent-cc``, ``resent-bcc``, ``reply-to``
A list of groups and/or addresses. Addresses are represented as
``dict``\s with two string fields: ``display_name`` (an empty string if
not given) and ``address``. Groups are represented as ``dict``\s with
a ``group`` field giving the name of the group and an ``addresses``
field giving a list of addresses in the group.

``message-id``
A single string

``content-type``
A ``dict`` containing a ``content_type`` field (a string of the form
``maintype/subtype``, e.g., ``"text/plain"``) and a ``params`` field (a
``dict`` of string keys & values). The ``charset`` and ``boundary``
parameters are discarded unless ``include_all`` is ``True``.

``date``
A ``datetime.datetime`` instance

``orig-date``
A ``datetime.datetime`` instance

``resent-date``
A list of ``datetime.datetime`` instances

``sender``
A single address ``dict``

``resent-sender``
A list of address ``dict``\s

``content-disposition``
A ``dict`` containing a ``disposition`` field (value either
``"inline"`` or ``"attachment"``) and a ``params`` field (a ``dict`` of
string keys & values)

``content-transfer-encoding``
A single string. This header is discarded unless ``include_all`` is
``True``.

``mime-version``
A single string. This header is discarded unless ``include_all`` is
``True``.

All other headers are represented as lists of strings.

``preamble``
The message's preamble__

__ https://docs.python.org/3/library/email.message.html
#email.message.EmailMessage.preamble

``content``
If the message is multipart, this is a list of message ``dict``\s,
structured the same way as the top-level ``dict``. If the message's
Content-Type is ``message/rfc822`` or ``message/external-body``, this is a
single message ``dict``. If the message's Content-Type is ``text/*``, this
is a ``str`` giving the contents of the message. Otherwise, it is a
``bytes`` giving the contents of the message.

``epilogue``
The message's epilogue__

__ https://docs.python.org/3/library/email.message.html
#email.message.EmailMessage.epilogue

An example: The ``email`` `examples page`__ in the Python docs includes an
example of constructing an HTML e-mail with an alternative plain text version
(It's the one with the subject "Ayons asperges pour le déjeuner"). Passing the
resulting ``EmailMessage`` object to ``email2dict()`` produces the following
output structure:

__ https://docs.python.org/3/library/email.examples.html

.. code:: python

{
"unixfrom": None,
"headers": {
"subject": "Ayons asperges pour le déjeuner",
"from": [
{
"display_name": "Pepé Le Pew",
"address": "[email protected]",
},
],
"to": [
{
"display_name": "Penelope Pussycat",
"address": "[email protected]",
},
{
"display_name": "Fabrette Pussycat",
"address": "[email protected]",
},
],
"content-type": {
"content_type": "multipart/alternative",
"params": {},
},
},
"preamble": None,
"content": [
{
"unixfrom": None,
"headers": {
"content-type": {
"content_type": "text/plain",
"params": {},
},
},
"preamble": None,
"content": (
"Salut!\n"
"\n"
"Cela ressemble à un excellent recipie[1] déjeuner.\n"
"\n"
"[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718\n"
"\n"
"--Pepé\n"
),
"epilogue": None,
},
{
"unixfrom": None,
"headers": {
"content-type": {
"content_type": "multipart/related",
"params": {},
},
},
"preamble": None,
"content": [
{
"unixfrom": None,
"headers": {
"content-type": {
"content_type": "text/html",
"params": {},
},
},
"preamble": None,
"content": (
"\n"
" \n"
" \n"
"

Salut!

\n"
"

Cela ressemble à un excellent\n"
" \n"
" recipie\n"
"
déjeuner.\n"
"

\n"
" \n"
" \n"
"\n"
),
"epilogue": None,
},
{
"unixfrom": None,
"headers": {
"content-type": {
"content_type": "image/png",
"params": {},
},
"content-disposition": {
"disposition": "inline",
"params": {},
},
"content-id": [""],
},
"preamble": None,
"content": b'IMAGE BLOB',
"epilogue": None,
},
],
"epilogue": None,
},
],
"epilogue": None,
}

``format_addresses()``
----------------------

.. code:: python

mailbits.format_addresses(addresses: Iterable[Union[str, Address, Group]], encode: bool = False) -> str

Convert an iterable of e-mail address strings (of the form
"``[email protected]``", without angle brackets or a display name),
``email.headerregistry.Address`` objects, and/or ``email.headerregistry.Group``
objects into a formatted string. If ``encode`` is ``False`` (the default),
non-ASCII characters are left as-is. If it is ``True``, non-ASCII display
names are converted into :RFC:`2047` encoded words, and non-ASCII domain names
are encoded using Punycode.

``message2email()``
-------------------

.. code:: python

mailbits.message2email(msg: email.message.Message) -> email.message.EmailMessage

Convert an instance of the old ``Message`` class (or one of its subclasses,
like a ``mailbox`` message class) to an instance of the new ``EmailMessage``
class with the ``default`` policy. If ``msg`` is already an ``EmailMessage``,
it is returned unchanged.

``parse_address()``
-------------------

.. code:: python

mailbits.parse_address(s: str) -> email.headerregistry.Address

Parse a single e-mail address — either a raw address like "``[email protected]``"
or a combined display name & address like "``Fabian Oh ``"
into an ``Address`` object.

``parse_addresses()``
---------------------

.. code:: python

mailbits.parse_addresses(s: Union[str, email.headerregistry.AddressHeader]) \
-> List[Union[email.headerregistry.Address, email.headerregistry.Group]]

Parse a formatted list of e-mail addresses or the contents of an
``EmailMessage``'s "To", "CC", "BCC", etc. header into a list of ``Address``
and/or ``Group`` objects.

``recipient_addresses()``
-------------------------

.. code:: python

mailbits.recipient_addresses(msg: email.message.EmailMessage) -> List[str]

Return a sorted list of all of the distinct e-mail addresses (not including
display names) in an ``EmailMessage``'s combined "To", "CC", and "BCC" headers.