Skip to content

Releases: seanthegeek/mailsuite

2.2.2

Choose a tag to compare

@seanthegeek seanthegeek released this 14 Jun 00:37
  • Honor config_reloading in the IDLE loop (#50)

2.2.1

Choose a tag to compare

@seanthegeek seanthegeek released this 24 May 16:41
1efc727
  • Require mail-parser>=4.2.1 (was >=4.1.2). 4.2.1 stops returning a phantom ('', '') entry for absent address headers and adds the CVE-2023-27043 strict address-parsing hardening. parse_email() already re-parsed these headers itself, so mailsuite's own output is unchanged — this guarantees the fixed dependency for code that reads the underlying mail-parser object.

2.2.0

Choose a tag to compare

@seanthegeek seanthegeek released this 23 May 03:58
3d5c009

Enhancements

  • Add a mailsuite.arc module for Authenticated Received Chain (RFC 8617) (#32):
    • seal_email() — add an ARC set to a message, extending any existing chain.
    • verify_arc_chain() — verify the chain and report the cv result.
    • Errors raise the new ARCError. Adds authres as a dependency (required by dkimpy's ARC sealing).
  • Add OAuth2 authentication to mailsuite.smtp.send_email, matching the IMAP client (#33):
    • Pass oauth2_token= (or oauth2_token_provider= for a fresh token at send time) with username= to authenticate without a password.
    • oauth2_mechanism= selects XOAUTH2 (default) or OAUTHBEARER; oauth2_vendor= supplies Yahoo's vendor string.
  • Add ClientAssertion auth to MSGraphConnection for federated / workload-identity scenarios that avoid a long-lived client secret. Pass client_assertion= with a signed-JWT assertion, or client_assertion_provider= (a zero-arg callable) to supply a fresh assertion each time azure-identity acquires a token. The assertion is exchanged for an access token via the JWT-bearer client-credentials grant — it is not itself a Graph access token (#31).

Fixes

  • Fix two GmailConnection bugs (#38):
    • fetch_messages(..., since=…) dropped the date filter on every page after the first, so paginated fetches returned messages older than since. The filter is now carried through pagination.
    • create_folder() did not clear the label-id cache, so a label looked up before creation stayed cached as missing — merge_folders(create=True) could then move messages to an empty destination label and orphan them. It now clears the cache like rename_folder / delete_folder / move_folder.
  • Fix several mailsuite.utils parsing bugs (#37):
    • parse_email() always returned reply-to as [], discarding the parsed Reply-To addresses; they are now preserved.
    • parse_email() raised re.error (or corrupted headers_string) when the Subject or Thread-Topic contained a backslash sequence such as \1; the value is now inserted literally.
    • parse_email_address() raised UnboundLocalError on input that was neither a str nor a tuple; it now raises a clear TypeError.
  • Fix mailsuite.smtp.send_email() not delivering to Cc and Bcc recipients. The SMTP envelope was built from message_to only — Cc/Bcc were appended after the envelope was captured — so the MTA delivered to the To addresses alone. The envelope now includes every recipient, and the caller's recipient lists are no longer mutated in place (#36).
  • Fix from_trusted_domain() raising TypeError when allow_multiple_authentication_results=True and the message carries multiple Authentication-Results headers (which parse_email represents as raw strings). Each header is now parsed before inspection, and the matched DMARC domain is lower-cased/stripped to match the single-header path (#35).
  • Fix IMAPClient folder normalization removing the personal-namespace prefix wherever it appeared inside a path rather than only at the start, so e.g. Projects/INBOX/old (prefix INBOX/) became INBOX/Projects/old. Only a leading prefix is now stripped before it is re-applied (#39).
  • Fix IMAPClient.move_messages() duplicating messages on servers without the MOVE capability. The copy/delete fallback operated on the full UID list inside the per-100 chunk loop, so moving more than 100 messages copied every message once per chunk. Each chunk is now copied and deleted on its own (#40).
  • Fix IMAPClient.delete_messages() raising on servers without the UIDPLUS capability. It always issued a UID EXPUNGE (expunge of specific UIDs), which requires UIDPLUS; on servers lacking it the error was uncaught. It now falls back to a plain EXPUNGE when UIDPLUS is unavailable (#42).
  • Fix parse_email() auto-reply detection (automatic_reply). It matched the literal auto_generated (underscore) and required both an Auto-Submitted and an X-Auto-Response-Suppress header, so it never flagged real auto-replies (Gmail sends Auto-Submitted: auto-replied, Microsoft 365 / Exchange sends auto-generated). It now follows RFC 3834 — any Auto-Submitted value other than no, or the presence of Exchange's X-Auto-Response-Suppress header (#47).
  • Fix the IMAP IDLE watch loop ignoring new mail on servers that signal it with an untagged EXISTS but no RECENT. The loop reacted only to RECENT; it now also reacts to EXISTS (the standard new-message signal) (#41).
  • Fix MSGraphConnection.fetch_messages() ignoring batch_size whenever since was set, so a date-filtered fetch pulled every matching page regardless of the requested cap. batch_size and since are independent now — the caller controls both (#46).
  • Fix the IMAP IDLE watch loop stacking a nested IDLE loop on every reconnect. A connection drop during watch() called reset_connection(), which re-ran __init__ and restarted IDLE recursively (duplicated callbacks, ever-deepening recursion on a long-running watch). Reconnects now re-arm the existing loop in place via a re-entrancy guard (#45).
  • Fix create_email() emitting an empty Cc: header when message_cc=[]. It now adds the header only when there are Cc addresses, matching the To handling (#44).

2.0.2

Choose a tag to compare

@seanthegeek seanthegeek released this 30 Apr 15:58
d422263
  • Fix RuntimeError: Event loop is closed in MSGraphConnection after the first Graph call. The internal _run helper used asyncio.run, which closed the event loop after each invocation; the Graph SDK's underlying httpx.AsyncClient keeps connection-pool resources bound to that loop, so the next call would fail. _run now retains a single persistent event loop across calls (domainaware/parsedmarc#742).
  • Detect "folder already exists" in MSGraphConnection.create_folder by HTTP status code (409 Conflict on the SDK's APIError.response_status_code) rather than by string-matching the exception message. The previous string match was brittle to error-message localization and Graph SDK changes.
  • Escape single quotes in folder-name OData filters in MSGraphConnection. Folder names containing an apostrophe (e.g. John's mail) previously produced a malformed displayName eq '…' filter expression that Graph rejected.

2.0.1

Choose a tag to compare

@seanthegeek seanthegeek released this 28 Apr 13:29
  • Add OAuth2 (XOAUTH2 / OAUTHBEARER) login support to mailsuite.imap.IMAPClient and mailsuite.mailbox.IMAPConnection. Pass oauth2_token= for a static access token, or oauth2_token_provider= (a zero-arg callable) to have a fresh token fetched on every connect/reconnect — recommended for long-running watch / IDLE loops where tokens expire mid-session. Yahoo's vendor string is supported via oauth2_vendor=. Workspace and Microsoft 365 users should still prefer the higher-level GmailConnection / MSGraphConnection backends, which handle token refresh end-to-end (#6).
  • Fix IMAPClient.select_folder (and other folder operations) on shared / other-users namespaces. _normalise_folder previously prepended the personal namespace prefix to every path, so e.g. user/colleague/Inbox became INBOX/user/colleague/Inbox. Paths already inside an RFC 2342 non-personal namespace are now passed through untouched (#13).
  • Fix the IMAPClient default port (was 933, now the standard 993).

2.0.0

Choose a tag to compare

@seanthegeek seanthegeek released this 28 Apr 04:56
6aee0ad
  • Add a new mailsuite.dkim module for DKIM key generation, public key extraction, TXT record generation, email signing, and signature verification
  • smtp.send_email() now accepts dkim_private_key, dkim_selector, dkim_domain, and dkim_additional_headers parameters for sending DKIM-signed mail
  • Add mailsuite.mailbox, a provider-agnostic mailbox abstraction lifted from parsedmarc
    • MailboxConnection ABC with create_folder, fetch_messages, fetch_message, delete_message, move_message, keepalive, watch, and send_message
    • IMAPConnection (built on mailsuite.imap.IMAPClient, IDLE-based watch loop)
    • MaildirConnection (built on the stdlib mailbox.Maildir)
    • MSGraphConnection (built on msgraph-sdk; sends through /users/{mailbox}/sendMail). Requires the mailsuite[msgraph] extra. Constructor accepts graph_url (any Microsoft Graph endpoint — sovereign clouds, dev/test endpoints, etc.) and token_cache_name (defaults to "mailsuite"; pass the previous installation's cache name — e.g. "parsedmarc" — for seamless migration so cached AuthenticationRecords carry over without re-prompting users).
    • GmailConnection (built on google-api-python-client; sends through users.messages.send). Requires the mailsuite[gmail] extra.
    • Optional cloud backends are loaded lazily via PEP 562 — importing mailsuite.mailbox never requires the extras to be installed; referencing the class surfaces a clear ImportError if they aren't.
    • send_message() raises NotImplementedError on the receive-only IMAP and Maildir backends; use mailsuite.smtp.send_email for standalone sending.
  • Add a comprehensive pytest test suite covering all modules, plus a GitHub Actions CI workflow that runs ruff, pyright, and pytest (with coverage) across supported Python versions