{"id":18084848,"url":"https://github.com/jwodder/mailbits","last_synced_at":"2025-04-12T20:09:48.818Z","repository":{"id":57426121,"uuid":"343019823","full_name":"jwodder/mailbits","owner":"jwodder","description":"Assorted e-mail utility functions","archived":false,"fork":false,"pushed_at":"2025-01-23T14:39:19.000Z","size":220,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T20:09:41.316Z","etag":null,"topics":["available-on-pypi","content-type","e-mail","e-mail-address","e-mail-comparison","e-mail-inspection","email","emailmessage","python","recipients"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jwodder.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-28T04:27:34.000Z","updated_at":"2025-01-23T14:39:20.000Z","dependencies_parsed_at":"2024-02-05T20:30:10.073Z","dependency_job_id":"5b9af209-3d9e-478b-a78d-016284e73822","html_url":"https://github.com/jwodder/mailbits","commit_stats":{"total_commits":76,"total_committers":1,"mean_commits":76.0,"dds":0.0,"last_synced_commit":"aefa122f675e186b221d1c2b775f35f42dba127a"},"previous_names":["jwodder/email2dict"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fmailbits","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fmailbits/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fmailbits/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fmailbits/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jwodder","download_url":"https://codeload.github.com/jwodder/mailbits/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625493,"owners_count":21135513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["available-on-pypi","content-type","e-mail","e-mail-address","e-mail-comparison","e-mail-inspection","email","emailmessage","python","recipients"],"created_at":"2024-10-31T15:08:25.852Z","updated_at":"2025-04-12T20:09:48.786Z","avatar_url":"https://github.com/jwodder.png","language":"Python","readme":"|repostatus| |ci-status| |coverage| |pyversions| |license|\n\n.. |repostatus| image:: https://www.repostatus.org/badges/latest/active.svg\n    :target: https://www.repostatus.org/#active\n    :alt: Project Status: Active — The project has reached a stable, usable\n          state and is being actively developed.\n\n.. |ci-status| image:: https://github.com/jwodder/mailbits/actions/workflows/test.yml/badge.svg\n    :target: https://github.com/jwodder/mailbits/actions/workflows/test.yml\n    :alt: CI Status\n\n.. |coverage| image:: https://codecov.io/gh/jwodder/mailbits/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/jwodder/mailbits\n\n.. |pyversions| image:: https://img.shields.io/pypi/pyversions/mailbits.svg\n    :target: https://pypi.org/project/mailbits/\n\n.. |license| image:: https://img.shields.io/github/license/jwodder/mailbits.svg\n    :target: https://opensource.org/licenses/MIT\n    :alt: MIT License\n\n`GitHub \u003chttps://github.com/jwodder/mailbits\u003e`_\n| `PyPI \u003chttps://pypi.org/project/mailbits/\u003e`_\n| `Issues \u003chttps://github.com/jwodder/mailbits/issues\u003e`_\n| `Changelog \u003chttps://github.com/jwodder/mailbits/blob/master/CHANGELOG.md\u003e`_\n\n``mailbits`` provides a small assortment of functions for working with the\nPython standard library's ``Message``/``EmailMessage``, ``Address``, and\n``Group`` types, as well as a couple other features.  It can parse \u0026 reassemble\nContent-Type strings, convert instances of the old ``Message`` class to the new\n``EmailMessage``, convert ``Message`` \u0026 ``EmailMessage`` instances into\nstructured ``dict``\\s, parse addresses, format address lists, and extract\nrecipients' raw e-mail addresses from an ``EmailMessage``.\n\n\nInstallation\n============\n``mailbits`` requires Python 3.8 or higher.  Just use `pip\n\u003chttps://pip.pypa.io\u003e`_ for Python 3 (You have pip, right?) to install it::\n\n    python3 -m pip install mailbits\n\n\nAPI\n===\n\n``ContentType``\n---------------\n\nThe ``ContentType`` class provides a representation of a parsed Content-Type\nheader value.  Parse Content-Type strings with the ``parse()`` classmethod,\ninspect the parts via the ``content_type``, ``maintype``, ``subtype``, and\n``params`` attributes (the last three of which can be mutated), convert back to\na string with ``str()``, and convert to ASCII bytes using encoded words for\nnon-ASCII with ``bytes()``.\n\n\u003e\u003e\u003e from mailbits import ContentType\n\u003e\u003e\u003e ct = ContentType.parse(\"text/plain; charset=utf-8; name*=utf-8''r%C3%A9sum%C3%A9.txt\")\n\u003e\u003e\u003e ct\nContentType(maintype='text', subtype='plain', params={'charset': 'utf-8', 'name': 'résumé.txt'})\n\u003e\u003e\u003e ct.content_type\n'text/plain'\n\u003e\u003e\u003e ct.maintype\n'text'\n\u003e\u003e\u003e ct.subtype\n'plain'\n\u003e\u003e\u003e ct.params\n{'charset': 'utf-8', 'name': 'résumé.txt'}\n\u003e\u003e\u003e str(ct)\n'text/plain; charset=\"utf-8\"; name=\"résumé.txt\"'\n\u003e\u003e\u003e bytes(ct)\nb'text/plain; charset=\"utf-8\"; name*=utf-8\\'\\'r%C3%A9sum%C3%A9.txt'\n\n\n``email2dict()``\n----------------\n\n.. code:: python\n\n    class MessageDict(TypedDict):\n        unixfrom: Optional[str]\n        headers: Dict[str, Any]\n        preamble: Optional[str]\n        content: Any\n        epilogue: Optional[str]\n\n    mailbits.email2dict(msg: email.message.Message, include_all: bool = False) -\u003e MessageDict\n\nConvert a ``Message`` object to a ``dict``.  All encoded text \u0026 bytes are\ndecoded into their natural values.\n\nNeed to examine a ``Message`` but find the builtin Python API too fiddly?  Need\nto check that a ``Message`` has the content \u0026 structure you expect?  Need to\ncompare two ``Message`` instances for equality?  Need to pretty-print the\nstructure of a ``Message``?  Then ``email2dict()`` has your back.\n\nBy default, any information specific to how the message is encoded (Content-Type\nparameters, Content-Transfer-Encoding, etc.) is not reported, as the focus is\non the actual content rather than the choices made in representing it.  To\ninclude this information anyway, set ``include_all`` to ``True``.\n\nThe output structure has the following fields:\n\n``unixfrom``\n    The \"From \" line marking the start of the message in a mbox, if any\n\n``headers``\n    A ``dict`` mapping lowercased header field names to values.  The following\n    headers have special representations:\n\n    ``subject``\n        A single string\n\n    ``from``, ``to``, ``cc``, ``bcc``, ``resent-from``, ``resent-to``, ``resent-cc``, ``resent-bcc``, ``reply-to``\n        A list of groups and/or addresses.  Addresses are represented as\n        ``dict``\\s with two string fields: ``display_name`` (an empty string if\n        not given) and ``address``.  Groups are represented as ``dict``\\s with\n        a ``group`` field giving the name of the group and an ``addresses``\n        field giving a list of addresses in the group.\n\n    ``message-id``\n        A single string\n\n    ``content-type``\n        A ``dict`` containing a ``content_type`` field (a string of the form\n        ``maintype/subtype``, e.g., ``\"text/plain\"``) and a ``params`` field (a\n        ``dict`` of string keys \u0026 values).  The ``charset`` and ``boundary``\n        parameters are discarded unless ``include_all`` is ``True``.\n\n    ``date``\n        A ``datetime.datetime`` instance\n\n    ``orig-date``\n        A ``datetime.datetime`` instance\n\n    ``resent-date``\n        A list of ``datetime.datetime`` instances\n\n    ``sender``\n        A single address ``dict``\n\n    ``resent-sender``\n        A list of address ``dict``\\s\n\n    ``content-disposition``\n        A ``dict`` containing a ``disposition`` field (value either\n        ``\"inline\"`` or ``\"attachment\"``) and a ``params`` field (a ``dict`` of\n        string keys \u0026 values)\n\n    ``content-transfer-encoding``\n        A single string.  This header is discarded unless ``include_all`` is\n        ``True``.\n\n    ``mime-version``\n        A single string.  This header is discarded unless ``include_all`` is\n        ``True``.\n\n    All other headers are represented as lists of strings.\n\n``preamble``\n    The message's preamble__\n\n    __ https://docs.python.org/3/library/email.message.html\n       #email.message.EmailMessage.preamble\n\n``content``\n    If the message is multipart, this is a list of message ``dict``\\s,\n    structured the same way as the top-level ``dict``.  If the message's\n    Content-Type is ``message/rfc822`` or ``message/external-body``, this is a\n    single message ``dict``.  If the message's Content-Type is ``text/*``, this\n    is a ``str`` giving the contents of the message.  Otherwise, it is a\n    ``bytes`` giving the contents of the message.\n\n``epilogue``\n    The message's epilogue__\n\n    __ https://docs.python.org/3/library/email.message.html\n       #email.message.EmailMessage.epilogue\n\nAn example: The ``email`` `examples page`__ in the Python docs includes an\nexample of constructing an HTML e-mail with an alternative plain text version\n(It's the one with the subject \"Ayons asperges pour le déjeuner\").  Passing the\nresulting ``EmailMessage`` object to ``email2dict()`` produces the following\noutput structure:\n\n__ https://docs.python.org/3/library/email.examples.html\n\n.. code:: python\n\n    {\n        \"unixfrom\": None,\n        \"headers\": {\n            \"subject\": \"Ayons asperges pour le déjeuner\",\n            \"from\": [\n                {\n                    \"display_name\": \"Pepé Le Pew\",\n                    \"address\": \"pepe@example.com\",\n                },\n            ],\n            \"to\": [\n                {\n                    \"display_name\": \"Penelope Pussycat\",\n                    \"address\": \"penelope@example.com\",\n                },\n                {\n                    \"display_name\": \"Fabrette Pussycat\",\n                    \"address\": \"fabrette@example.com\",\n                },\n            ],\n            \"content-type\": {\n                \"content_type\": \"multipart/alternative\",\n                \"params\": {},\n            },\n        },\n        \"preamble\": None,\n        \"content\": [\n            {\n                \"unixfrom\": None,\n                \"headers\": {\n                    \"content-type\": {\n                        \"content_type\": \"text/plain\",\n                        \"params\": {},\n                    },\n                },\n                \"preamble\": None,\n                \"content\": (\n                    \"Salut!\\n\"\n                    \"\\n\"\n                    \"Cela ressemble à un excellent recipie[1] déjeuner.\\n\"\n                    \"\\n\"\n                    \"[1] http://www.yummly.com/recipe/Roasted-Asparagus-Epicurious-203718\\n\"\n                    \"\\n\"\n                    \"--Pepé\\n\"\n                ),\n                \"epilogue\": None,\n            },\n            {\n                \"unixfrom\": None,\n                \"headers\": {\n                    \"content-type\": {\n                        \"content_type\": \"multipart/related\",\n                        \"params\": {},\n                    },\n                },\n                \"preamble\": None,\n                \"content\": [\n                    {\n                        \"unixfrom\": None,\n                        \"headers\": {\n                            \"content-type\": {\n                                \"content_type\": \"text/html\",\n                                \"params\": {},\n                            },\n                        },\n                        \"preamble\": None,\n                        \"content\": (\n                            \"\u003chtml\u003e\\n\"\n                            \"  \u003chead\u003e\u003c/head\u003e\\n\"\n                            \"  \u003cbody\u003e\\n\"\n                            \"    \u003cp\u003eSalut!\u003c/p\u003e\\n\"\n                            \"    \u003cp\u003eCela ressemble à un excellent\\n\"\n                            \"        \u003ca href=\\\"http://www.yummly.com/recipe/Roasted-Asparagus-\"\n                            \"Epicurious-203718\\\"\u003e\\n\"\n                            \"            recipie\\n\"\n                            \"        \u003c/a\u003e déjeuner.\\n\"\n                            \"    \u003c/p\u003e\\n\"\n                            \"    \u003cimg src=\\\"cid:RANDOM_MESSAGE_ID\\\" /\u003e\\n\"\n                            \"  \u003c/body\u003e\\n\"\n                            \"\u003c/html\u003e\\n\"\n                        ),\n                        \"epilogue\": None,\n                    },\n                    {\n                        \"unixfrom\": None,\n                        \"headers\": {\n                            \"content-type\": {\n                                \"content_type\": \"image/png\",\n                                \"params\": {},\n                            },\n                            \"content-disposition\": {\n                                \"disposition\": \"inline\",\n                                \"params\": {},\n                            },\n                            \"content-id\": [\"\u003cRANDOM_MESSAGE_ID\u003e\"],\n                        },\n                        \"preamble\": None,\n                        \"content\": b'IMAGE BLOB',\n                        \"epilogue\": None,\n                    },\n                ],\n                \"epilogue\": None,\n            },\n        ],\n        \"epilogue\": None,\n    }\n\n\n``format_addresses()``\n----------------------\n\n.. code:: python\n\n    mailbits.format_addresses(addresses: Iterable[Union[str, Address, Group]], encode: bool = False) -\u003e str\n\nConvert an iterable of e-mail address strings (of the form\n\"``foo@example.com``\", without angle brackets or a display name),\n``email.headerregistry.Address`` objects, and/or ``email.headerregistry.Group``\nobjects into a formatted string.  If ``encode`` is ``False`` (the default),\nnon-ASCII characters are left as-is.  If it is ``True``, non-ASCII display\nnames are converted into :RFC:`2047` encoded words, and non-ASCII domain names\nare encoded using Punycode.\n\n\n``message2email()``\n-------------------\n\n.. code:: python\n\n    mailbits.message2email(msg: email.message.Message) -\u003e email.message.EmailMessage\n\nConvert an instance of the old ``Message`` class (or one of its subclasses,\nlike a ``mailbox`` message class) to an instance of the new ``EmailMessage``\nclass with the ``default`` policy.  If ``msg`` is already an ``EmailMessage``,\nit is returned unchanged.\n\n\n``parse_address()``\n-------------------\n\n.. code:: python\n\n    mailbits.parse_address(s: str) -\u003e email.headerregistry.Address\n\nParse a single e-mail address — either a raw address like \"``foo@example.com``\"\nor a combined display name \u0026 address like \"``Fabian Oh \u003cfoo@example.com\u003e``\"\ninto an ``Address`` object.\n\n\n``parse_addresses()``\n---------------------\n\n.. code:: python\n\n    mailbits.parse_addresses(s: Union[str, email.headerregistry.AddressHeader]) \\\n        -\u003e List[Union[email.headerregistry.Address, email.headerregistry.Group]]\n\nParse a formatted list of e-mail addresses or the contents of an\n``EmailMessage``'s \"To\", \"CC\", \"BCC\", etc. header into a list of ``Address``\nand/or ``Group`` objects.\n\n\n``recipient_addresses()``\n-------------------------\n\n.. code:: python\n\n    mailbits.recipient_addresses(msg: email.message.EmailMessage) -\u003e List[str]\n\nReturn a sorted list of all of the distinct e-mail addresses (not including\ndisplay names) in an ``EmailMessage``'s combined \"To\", \"CC\", and \"BCC\" headers.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwodder%2Fmailbits","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjwodder%2Fmailbits","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwodder%2Fmailbits/lists"}