API
mailsuite.imap
- class mailsuite.imap.IMAPClient(host: str, username: str | None = None, password: str | None = None, port: int = 993, ssl: bool = True, ssl_context: SSLContext | None = None, verify: bool = True, timeout: int = 30, max_retries: int = 4, initial_folder: str = 'INBOX', idle_callback=None, idle_timeout: int = 30, oauth2_token: str | None = None, oauth2_token_provider: Callable[[], str] | None = None, oauth2_mechanism: str = 'XOAUTH2', oauth2_vendor: str | None = None, config_reloading: Callable[[], bool] | None = None)[source]
A simplified IMAP client
- create_folder(folder: str, _attempt: int = 1)[source]
Creates an IMAP folder at the given path
- Parameters:
folder – The path of the folder to create
_attempt – The attempt number
- delete_messages(messages: list[int] | list[str] | str | int, silent: bool = True, _attempt: int = 1)[source]
Deletes the given messages by Message UIDs
- Parameters:
messages – A list of UIDs of messages to delete
silent – Do it silently
_attempt – The attempt number
- fetch_message(msg_uid: int, parse: bool = False, _attempt: int = 1) str | dict[source]
Fetch a message by UID, and optionally parse it
- Parameters:
msg_uid – The message UID
parse – Return parsed results from mailparser
_attempt – The attempt number
- Returns:
The raw mail message, including headers dict: A parsed email message
- Return type:
str
mailsuite.smtp
- mailsuite.smtp.send_email(host: str, message_from: str, message_to: list[str] | None = None, message_cc: list | None = None, message_bcc: list | None = None, port: int = 0, require_encryption: bool = False, verify: bool = True, username: str | None = None, password: str | None = None, oauth2_token: str | None = None, oauth2_token_provider: Callable[[], str] | None = None, oauth2_mechanism: str = 'XOAUTH2', oauth2_vendor: str | None = None, envelope_from: str | None = None, subject: str | None = None, message_headers: dict | None = None, attachments: list[tuple[str, bytes]] | None = None, plain_message: str | None = None, html_message: str | None = None, dkim_private_key: str | None = None, dkim_selector: str | None = None, dkim_domain: str | None = None, dkim_additional_headers: list[str] | None = None)[source]
Send an email using a SMTP relay
- Parameters:
host – Mail server hostname or IP address
message_from – The value of the message “From” header
message_to – A list of addresses to send mail to
message_cc – A list of addresses to Carbon Copy (CC)
message_bcc – A list of addresses to Blind Carbon Copy (BCC)
port – Port to use
require_encryption – Require a SSL/TLS connection from the start
verify – Verify the SSL/TLS certificate
username – An optional username
password – An optional password (omit when using OAuth2)
oauth2_token – A static OAuth2 access token. Provide this (or
oauth2_token_provider) together withusernameto authenticate with OAuth2 instead of a password.oauth2_token_provider – A zero-arg callable returning a current OAuth2 access token, invoked at send time so a fresh token is used. Takes precedence over
oauth2_token.oauth2_mechanism –
"XOAUTH2"(default — Gmail / Microsoft 365 / Yahoo) or"OAUTHBEARER"(Gmail’s standards-track variant)oauth2_vendor – Optional vendor string required by Yahoo’s XOAUTH2 implementation (XOAUTH2 only)
envelope_from – Overrides the SMTP envelope “mail from” header
subject – The message subject
message_headers – Custom message headers
attachments – A list of tuples, containing filenames and bytes
plain_message – The plain text message body
html_message – The HTML message body
dkim_private_key – A PEM-encoded RSA private key. When provided (along with
dkim_selectoranddkim_domain), the message is DKIM-signed before sending.dkim_selector – The DKIM selector to use when signing
dkim_domain – The DKIM signing domain (defaults to the domain of
message_fromwhendkim_private_keyis set butdkim_domainis not)dkim_additional_headers – Additional header names to include in the DKIM signature. Headers not present in the message are skipped.
mailsuite.dkim
DKIM key management and email signing utilities
- mailsuite.dkim.generate_dkim_keypair(key_size: int = 2048) tuple[str, str][source]
Generates a DKIM RSA keypair
- Parameters:
key_size – The RSA key size in bits (1024 minimum, 2048 recommended)
Returns: A tuple of
(private_key_pem, public_key_b64)
- mailsuite.dkim.generate_dkim_private_key(key_size: int = 2048) str[source]
Generates a new RSA private key suitable for DKIM signing
- Parameters:
key_size – The RSA key size in bits (1024 minimum, 2048 recommended)
Returns: A PEM-encoded private key string
- mailsuite.dkim.generate_dkim_txt_record(public_key: str | bytes, selector: str = 'default', domain: str | None = None, flags: str | None = None, note: str | None = None) str[source]
Generates a DKIM TXT record
- Parameters:
public_key – A base64-encoded public key, or a PEM-encoded private or public key (the base64 is extracted automatically)
selector – The DKIM selector
domain – An optional domain. When provided, the return value includes the full DNS owner name (
selector._domainkey.domain) so it shows exactly where the record must be placed.flags – An optional value for the
t=flags tag (e.g."y"for testing mode)note – An optional value for the
n=notes tag
- Returns:
When
domainis given, the full DNS record (owner name, class, type, and quoted value) as a single line. Otherwise, just the record value (v=DKIM1; ...).
- mailsuite.dkim.get_dkim_public_key(private_key: str | bytes) str[source]
Derives the DKIM public key from a private key
- Parameters:
private_key – A PEM-encoded RSA private key (PKCS#1 or PKCS#8)
- Returns: A base64-encoded
SubjectPublicKeyInfovalue (suitable for the p=tag of a DKIM TXT record)
- mailsuite.dkim.sign_email(message: str | bytes, selector: str, domain: str, private_key: str | bytes, additional_headers: list[str] | None = None, canonicalize: tuple[bytes, bytes] = (b'relaxed', b'relaxed'), identity: str | None = None) str | bytes[source]
DKIM-signs an email and returns the signed RFC 822 message
The default set of headers signed includes
From,To,Cc,Reply-To,Subject,Date,Message-ID,In-Reply-To,References,MIME-Version,Content-Type,Content-Transfer-Encoding,List-Unsubscribe, andList-Unsubscribe-Post— withFrom,To,Cc, andSubjectoversigned (signed twice) to prevent header addition attacks. Headers that are not present in the message are skipped.- Parameters:
message – An RFC 822 message
selector – The DKIM selector
domain – The signing domain
private_key – A PEM-encoded RSA private key
additional_headers – Additional header names to sign. Headers not present in the message are skipped.
canonicalize – A tuple of (header, body) canonicalization algorithms. Defaults to
(b"relaxed", b"relaxed").identity – An optional
i=value (defaults to@+domain)
- Returns: The signed RFC 822 message. The return type matches the input
type —
strin,strout;bytesin,bytesout.
- mailsuite.dkim.verify_email(message: str | bytes, timeout: float = 5.0, minkey: int = 1024, dns_func: Callable[[str], bytes] | None = None) dict[source]
Verifies the DKIM signature(s) on an RFC 822 message
Each
DKIM-Signatureheader in the message is verified independently via DNS. The result reports per-signature outcomes plus an overallvalidflag (Truewhen at least one signature verifies).- Parameters:
message – An RFC 822 message
timeout – DNS lookup timeout in seconds
minkey – The minimum acceptable RSA key size in bits
dns_func – An optional function taking a DNS name and returning the raw TXT record value as bytes. Useful for testing or for using a custom resolver. Defaults to dkimpy’s built-in resolver.
Returns: A dict with the following keys:
valid(bool):Trueif at least one signature verifiedsignatures(list): per-signature results, each a dict with:domain(str): thed=signing domainselector(str): thes=selectorvalid(bool): whether this signature verifiederror(strorNone): error message whenvalidisFalse, otherwiseNone
mailsuite.arc
Authenticated Received Chain (ARC) sealing and verification (RFC 8617)
ARC lets a sequence of intermediaries (mailing lists, forwarders, gateways)
record the email authentication results they observed, so that a later
receiver can trust those results even when SPF/DKIM/DMARC break in transit.
Each hop adds an ARC set of three header fields keyed by an instance
number (i=):
ARC-Authentication-Results(AAR) — a snapshot of theAuthentication-Resultsthis hop produced.ARC-Message-Signature(AMS) — a DKIM-like signature over the message as this hop saw it.ARC-Seal(AS) — a signature over the ARC header fields, binding the chain together and recording its cumulative validity (cv=).
This module wraps dkimpy’s ARC implementation behind an API shaped
like mailsuite.dkim.
- mailsuite.arc.seal_email(message: str | bytes, selector: str, domain: str, private_key: str | bytes, authserv_id: str, signed_headers: list[str] | None = None, timestamp: int | None = None) str | bytes[source]
Adds an ARC set (seal) to an email and returns the sealed RFC 822 message
The new ARC set is prepended to the message. If the message already carries one or more ARC sets, this adds the next instance and extends the chain.
The message must contain an
Authentication-Resultsheader whose authserv-id equalsauthserv_id— that is the authentication this hop is attesting to, and it is copied into theARC-Authentication-Resultsheader. Per RFC 8617 the chain is sealed only when such results exist. If none match — or, when extending an existing chain, the matching results record no prior ARC result (arc=) to continue from — no ARC set is produced andARCErroris raised.- Parameters:
message – An RFC 822 message
selector – The DKIM selector for the sealing domain
domain – The sealing (ADMD) domain
private_key – A PEM-encoded RSA private key
authserv_id – The authentication-service identifier of this hop (the authserv-id used in its
Authentication-Resultsheaders, often the receiving host’s name). OnlyAuthentication-Resultsheaders carrying this id are folded into the seal.signed_headers – Header names the
ARC-Message-Signatureshould cover. Defaults to dkimpy’s recommended set — the headers present in the message that it lists as SHOULD-sign (From,To,Cc,Subject,Date,Message-ID, theList-*headers, etc.), withFromoversigned.Frommust be included.timestamp – The
t=value (epoch seconds) stamped into the AMS and AS. Defaults to the current time.
- Returns: The sealed RFC 822 message. The return type matches the input
type —
strin,strout;bytesin,bytesout.
- Raises:
ARCError – If the message has no matching
Authentication-Resultsheader (nothing to seal), an existing chain cannot be continued, or the inputs are otherwise malformed (e.g.Fromis not signed).
- mailsuite.arc.verify_arc_chain(message: str | bytes, minkey: int = 1024, dns_func: Callable[[str], bytes] | None = None) dict[source]
Verifies the ARC chain on an RFC 822 message
The chain validation value (
cv) summarises the whole chain:"pass"— every ARC set verified and the chain is intact."none"— the message is not ARC sealed."fail"— the chain is broken (a signature did not verify, a seal reported failure, or an instance reported an invalid status).
Per RFC 8617 the most recent
ARC-Message-Signaturemust validate and everyARC-Sealin the chain must validate for a"pass".- Parameters:
message – An RFC 822 message
minkey – The minimum acceptable RSA key size in bits
dns_func – An optional function taking a DNS name and returning the raw TXT record value as bytes. Useful for testing or for using a custom resolver. Defaults to dkimpy’s built-in resolver.
Returns: A dict with the following keys:
valid(bool):Trueonly whencvis"pass"cv(str): the chain validation value ("pass","fail", or"none")reason(str): a human-readable explanation of the resultinstances(list): per-ARC-set results in ascending instance order, each a dict with:instance(int): thei=instance numberams_domain(str): the AMSd=signing domainams_selector(str): the AMSs=selectorams_valid(bool): whether the AMS verifiedas_domain(str): the ASd=signing domainas_selector(str): the ASs=selectoras_valid(bool): whether the AS verifiedcv(str): thecv=value recorded in this AS
mailsuite.mailbox
Abstract base class for mailbox connections
- exception mailsuite.mailbox.base.FolderExistsError[source]
Raised when a folder/label operation targets a name that is already taken — e.g.
MailboxConnection.rename_folder()orMailboxConnection.move_folder()onto an existing name.
- exception mailsuite.mailbox.base.FolderNotFoundError[source]
Raised when a folder/label referenced by an operation does not exist — e.g. the source of a
MailboxConnection.move_folder()/MailboxConnection.merge_folders(), or a destination when itscreateparameter is leftFalse.
- class mailsuite.mailbox.base.MailboxConnection[source]
A provider-agnostic interface for a mailbox
Subclasses implement the methods for a specific protocol (IMAP, Microsoft Graph, Gmail, Maildir, etc.). Methods that don’t apply to a given backend raise
NotImplementedError.- fetch_message(message_id: Any, **kwargs: Any) str[source]
Fetch the raw RFC 822 contents of a message by identifier
- fetch_messages(reports_folder: str, **kwargs: Any) list[source]
Return a list of message identifiers in the given folder
- folder_exists(folder_name: str) bool[source]
Return
Trueif the named folder/label exists in the mailbox- Parameters:
folder_name – The folder/label name (or path) to check
- merge_folders(sources: str | list[str], destination: str, create: bool = False, keep_source_folders: bool = False) None[source]
Move the contents of one or more folders into another
Every message in each source folder is moved into
destination.- Parameters:
sources – A source folder path, or a list of them.
destination – The folder to move messages into.
create – Create
destinationif it doesn’t already exist. WhenFalse(default), a missing destination raisesFolderNotFoundError.keep_source_folders – Leave the emptied source folders in place. When
False(default), each source folder is deleted after its messages have been moved.
- Raises:
FolderNotFoundError – If a source (or, with
create=False, the destination) does not exist.
- move_folder(source: str, new_path: str | None = None, new_parent: str | None = None, create: bool = False) None[source]
Relocate a folder (and its contents) to a new location
Give exactly one of
new_pathornew_parent:new_pathis the folder’s complete new path, e.g.move_folder("Archive/Forensic", new_path="Reports/Failure").new_parentis the folder to movesourceunder, keeping its own leaf name, e.g.move_folder("Archive/Forensic", new_parent="Reports")yieldsReports/Forensic.
- Parameters:
source – Path of the folder to move. Must exist.
new_path – The complete new path for the folder.
new_parent – The parent folder to move
sourceunder (its leaf name is preserved). Use""for the mailbox root.create – Create the destination’s parent path if it doesn’t already exist. When
False(default), a missing parent raisesFolderNotFoundError.
- Raises:
ValueError – If not exactly one of
new_path/new_parentis given.FolderNotFoundError – If
source(or, withcreate=False, the destination parent) does not exist.FolderExistsError – If the target path is already taken.
Note
On Gmail there are no real folders — only labels nested by a
/naming convention — so a move renames the label’s path and does not relocate independent descendant labels.
- rename_folder(old_name: str, new_name: str) None[source]
Rename a folder/label in the mailbox
Implementations call
_ensure_no_folder_conflict()first, so a rename onto an existing name raisesFolderExistsErrorconsistently rather than each backend’s native behavior.- Parameters:
old_name – The current folder/label name (or path)
new_name – The new folder/label name
- Raises:
FolderExistsError – If
new_namealready exists.
- send_message(message_from: str, message_to: list[str] | None = None, message_cc: list[str] | None = None, message_bcc: list[str] | None = None, subject: str | None = None, message_headers: dict | None = None, attachments: list[tuple[str, bytes]] | None = None, plain_message: str | None = None, html_message: str | None = None, save_to_sent_items: bool = True) str | None[source]
Send a message through this mailbox’s native send API (when supported)
Backends without a native send (IMAP, Maildir) raise
NotImplementedError. Usemailsuite.smtp.send_email()directly when you need to send mail without a mailbox.- Parameters:
message_from – The value of the
Fromheadermessage_to – A list of recipient addresses
message_cc – A list of Cc addresses
message_bcc – A list of Bcc addresses
subject – The message subject
message_headers – Additional headers
attachments – A list of
(filename, bytes)tuplesplain_message – The plain-text body
html_message – The HTML body
save_to_sent_items – Whether to save a copy to Sent Items (Microsoft Graph only; Gmail always saves a copy). Default
Truefor backward compatibility.
- Returns:
A provider-specific message identifier when available, otherwise
None.
- watch(check_callback: Callable[[MailboxConnection], None], check_timeout: int, config_reloading: Callable[[], bool] | None = None) None[source]
Watch the mailbox for new messages, invoking
check_callbackwhen new mail arrives or on a polling interval- Parameters:
check_callback – Called with this
MailboxConnectioninstance whenever the watcher fires.check_timeout – Polling interval (or IDLE timeout) in seconds.
config_reloading – Optional zero-argument callable. When it returns a truthy value, the watcher exits cleanly so the caller can reload configuration.
IMAP mailbox backend
- class mailsuite.mailbox.imap.IMAPConnection(host: str, user: str, password: str | None = None, port: int = 993, ssl: bool = True, verify: bool = True, timeout: int = 30, max_retries: int = 4, oauth2_token: str | None = None, oauth2_token_provider: Callable[[], str] | None = None, oauth2_mechanism: str = 'XOAUTH2', oauth2_vendor: str | None = None)[source]
A
MailboxConnectionbacked by IMAPWraps
mailsuite.imap.IMAPClientand adds theMailboxConnectionsemantics (folder/label management, polling via IDLE, etc.).IMAP is a mail-access protocol with no send capability —
send_message()raisesNotImplementedError. Usemailsuite.smtp.send_email()for sending.- fetch_message(message_id: Any, **kwargs: Any) str[source]
Fetch the raw RFC 822 contents of a message by identifier
- fetch_messages(reports_folder: str, **kwargs: Any) list[source]
Return a list of message identifiers in the given folder
- folder_exists(folder_name: str) bool[source]
Return
Trueif the named folder/label exists in the mailbox- Parameters:
folder_name – The folder/label name (or path) to check
- rename_folder(old_name: str, new_name: str) None[source]
Rename a folder/label in the mailbox
Implementations call
_ensure_no_folder_conflict()first, so a rename onto an existing name raisesFolderExistsErrorconsistently rather than each backend’s native behavior.- Parameters:
old_name – The current folder/label name (or path)
new_name – The new folder/label name
- Raises:
FolderExistsError – If
new_namealready exists.
- send_message(*args: Any, **kwargs: Any) str | None[source]
Send a message through this mailbox’s native send API (when supported)
Backends without a native send (IMAP, Maildir) raise
NotImplementedError. Usemailsuite.smtp.send_email()directly when you need to send mail without a mailbox.- Parameters:
message_from – The value of the
Fromheadermessage_to – A list of recipient addresses
message_cc – A list of Cc addresses
message_bcc – A list of Bcc addresses
subject – The message subject
message_headers – Additional headers
attachments – A list of
(filename, bytes)tuplesplain_message – The plain-text body
html_message – The HTML body
save_to_sent_items – Whether to save a copy to Sent Items (Microsoft Graph only; Gmail always saves a copy). Default
Truefor backward compatibility.
- Returns:
A provider-specific message identifier when available, otherwise
None.
- watch(check_callback: Callable[[MailboxConnection], None], check_timeout: int, config_reloading: Callable[[], bool] | None = None) None[source]
Watch for new messages over an IDLE connection and dispatch each batch to
check_callback
Maildir mailbox backend
- class mailsuite.mailbox.maildir.MaildirConnection(maildir_path: str, maildir_create: bool = False)[source]
A
MailboxConnectionbacked by an on-disk MaildirUseful for local processing of messages dropped into a Maildir by an MTA (e.g. postfix delivering DMARC reports). Maildir has no concept of sending —
send_message()raisesNotImplementedError.- fetch_message(message_id: Any, **kwargs: Any) str[source]
Fetch the raw RFC 822 contents of a message by identifier
- fetch_messages(reports_folder: str, **kwargs: Any) list[source]
Return a list of message identifiers in the given folder
- folder_exists(folder_name: str) bool[source]
Return
Trueif the named folder/label exists in the mailbox- Parameters:
folder_name – The folder/label name (or path) to check
- rename_folder(old_name: str, new_name: str) None[source]
Rename a folder/label in the mailbox
Implementations call
_ensure_no_folder_conflict()first, so a rename onto an existing name raisesFolderExistsErrorconsistently rather than each backend’s native behavior.- Parameters:
old_name – The current folder/label name (or path)
new_name – The new folder/label name
- Raises:
FolderExistsError – If
new_namealready exists.
- send_message(*args: Any, **kwargs: Any) str | None[source]
Send a message through this mailbox’s native send API (when supported)
Backends without a native send (IMAP, Maildir) raise
NotImplementedError. Usemailsuite.smtp.send_email()directly when you need to send mail without a mailbox.- Parameters:
message_from – The value of the
Fromheadermessage_to – A list of recipient addresses
message_cc – A list of Cc addresses
message_bcc – A list of Bcc addresses
subject – The message subject
message_headers – Additional headers
attachments – A list of
(filename, bytes)tuplesplain_message – The plain-text body
html_message – The HTML body
save_to_sent_items – Whether to save a copy to Sent Items (Microsoft Graph only; Gmail always saves a copy). Default
Truefor backward compatibility.
- Returns:
A provider-specific message identifier when available, otherwise
None.
- watch(check_callback: Callable[[MailboxConnection], None], check_timeout: int, config_reloading: Callable[[], bool] | None = None) None[source]
Watch the mailbox for new messages, invoking
check_callbackwhen new mail arrives or on a polling interval- Parameters:
check_callback – Called with this
MailboxConnectioninstance whenever the watcher fires.check_timeout – Polling interval (or IDLE timeout) in seconds.
config_reloading – Optional zero-argument callable. When it returns a truthy value, the watcher exits cleanly so the caller can reload configuration.
Microsoft Graph mailbox backend
- class mailsuite.mailbox.graph.MSGraphConnection(auth_method: str, mailbox: str, client_id: str, client_secret: str | None, username: str | None, password: str | None, tenant_id: str, token_file: str, allow_unencrypted_storage: bool, certificate_path: str | None = None, certificate_password: str | bytes | None = None, graph_url: str | None = None, token_cache_name: str = 'mailsuite', client_assertion: str | None = None, client_assertion_provider: Callable[[], str] | None = None)[source]
A
MailboxConnectionbacked by Microsoft GraphSupports DeviceCode, UsernamePassword, ClientSecret, ClientAssertion, and Certificate auth via
azure.identity. Send mail goes through/users/{mailbox}/sendMailwith a structuredMessagebody; the request setssaveToSentItems, so a copy is saved to Sent Items by default. Passsave_to_sent_items=Falseto skip saving a copy.Required Microsoft Graph API permissions on the app registration (combine as needed):
Read-only (
fetch_message,fetch_messages):Mail.ReadRead + modify (mark read, delete, move, create folder):
Mail.ReadWriteSend mail (
send_message):Mail.Send
Delegated flows (
DeviceCode,UsernamePassword) targeting a shared mailbox (i.e.mailbox != username) use the.Sharedvariants —Mail.Read.Shared,Mail.ReadWrite.Shared,Mail.Send.Shared. App-only flows (ClientSecret,ClientAssertion,Certificate) do not need the.Sharedvariants. See the README “Microsoft Graph permissions” section for the full mapping.Note: delegated flows always request
Mail.ReadWriteat authenticate time, so even read-only callers must consent to at leastMail.ReadWrite.Requires the
msgraphextra:pip install mailsuite[msgraph]
- fetch_message(message_id: Any, **kwargs: Any) str[source]
Fetch the raw RFC 822 contents of a message by identifier
- fetch_messages(reports_folder: str, **kwargs: Any) list[str][source]
Return a list of message identifiers in the given folder
- folder_exists(folder_name: str) bool[source]
Return
Trueif the folder (by name orparent/childpath) resolves to an id,Falseif no such folder exists. A failed listing call (auth/network) propagates asRuntimeErrorrather than being reported as a missing folder.
- mark_message_read(message_id: str) None[source]
Mark the message with the given id as read (Graph-only; not part of the
MailboxConnectioninterface)
- rename_folder(old_name: str, new_name: str) None[source]
Rename a mail folder in place
Issues
PATCH /users/{mailbox}/mailFolders/{id}with a newdisplayName(requiresMail.ReadWrite). Graph’s update operation only changes the folder’s display name — it does not move the folder to a different parent (relocating a folder is a separatemoveaction). Accordingly, only the leaf segment ofnew_nameis used as the new display name, so passing aparent/childpath won’t create a folder whose name literally contains a slash. The folder’s id is unchanged by a rename.- Parameters:
old_name – The current folder name or
parent/childpathnew_name – The new display name (leaf segment is used)
- Raises:
FolderExistsError – If
new_namealready resolves to a folder.
- send_message(message_from: str, message_to: list[str] | None = None, message_cc: list[str] | None = None, message_bcc: list[str] | None = None, subject: str | None = None, message_headers: dict | None = None, attachments: list[tuple[str, bytes]] | None = None, plain_message: str | None = None, html_message: str | None = None, save_to_sent_items: bool = True) str | None[source]
Send a message through this mailbox’s native send API (when supported)
Backends without a native send (IMAP, Maildir) raise
NotImplementedError. Usemailsuite.smtp.send_email()directly when you need to send mail without a mailbox.- Parameters:
message_from – The value of the
Fromheadermessage_to – A list of recipient addresses
message_cc – A list of Cc addresses
message_bcc – A list of Bcc addresses
subject – The message subject
message_headers – Additional headers
attachments – A list of
(filename, bytes)tuplesplain_message – The plain-text body
html_message – The HTML body
save_to_sent_items – Whether to save a copy to Sent Items (Microsoft Graph only; Gmail always saves a copy). Default
Truefor backward compatibility.
- Returns:
A provider-specific message identifier when available, otherwise
None.
- watch(check_callback: Callable[[MailboxConnection], None], check_timeout: int, config_reloading: Callable[[], bool] | None = None) None[source]
Poll the mailbox at
check_timeout-second intervals
Gmail mailbox backend
- class mailsuite.mailbox.gmail.GmailConnection(token_file: str, credentials_file: str, scopes: list[str], include_spam_trash: bool, reports_folder: str, oauth2_port: int, paginate_messages: bool, auth_mode: str = 'installed_app', service_account_user: str | None = None)[source]
A
MailboxConnectionbacked by the Gmail APISends mail through
users.messages.sendwith the message built bymailsuite.utils.create_email(). Sending requires a scope that includes the send permission (gmail.send,gmail.modify, or fullmail.google.com).Requires the
gmailextra:pip install mailsuite[gmail]
- fetch_message(message_id: Any, **kwargs: Any) str[source]
Fetch the raw RFC 822 contents of a message by identifier
- fetch_messages(reports_folder: str, **kwargs: Any) list[str][source]
Return a list of message identifiers in the given folder
- folder_exists(folder_name: str) bool[source]
Return
Trueif the named folder/label exists in the mailbox- Parameters:
folder_name – The folder/label name (or path) to check
- rename_folder(old_name: str, new_name: str) None[source]
Rename a label
Gmail has no folders — only labels — so this backend maps the
MailboxConnection“folder” concept onto labels. Renaming patches the label’s display name; the label’s immutable id is preserved, so existing message associations (and any cached id) stay valid.Only user labels can be renamed. Renaming a system label (
INBOX,SENT,SPAM, etc.) is rejected by Gmail and surfaces as agoogleapiclient.errors.HttpError. Nested labels are independent: renamingWorkdoes not touch a label namedWork/Projects.- Parameters:
old_name – The current label name (or id)
new_name – The new label display name
- Raises:
FolderExistsError – If a label named
new_namealready exists.
- send_message(message_from: str, message_to: list[str] | None = None, message_cc: list[str] | None = None, message_bcc: list[str] | None = None, subject: str | None = None, message_headers: dict | None = None, attachments: list[tuple[str, bytes]] | None = None, plain_message: str | None = None, html_message: str | None = None, save_to_sent_items: bool = True) str | None[source]
Send a message through the Gmail API.
The
save_to_sent_itemsparameter is accepted for API parity withMSGraphConnection.send_message()but is ignored — Gmail always saves a copy to Sent Mail.
- watch(check_callback: Callable[[MailboxConnection], None], check_timeout: int, config_reloading: Callable[[], bool] | None = None) None[source]
Poll the mailbox at
check_timeout-second intervals
mailsuite.utils
- mailsuite.utils.convert_outlook_msg(msg_bytes: bytes) str[source]
Uses the
msgconvertPerl utility to convert an Outlook .msg file to standard RFC 822 formatWarning
Anomalies are introduced during conversion that make the results unsuitable for forensic analysis.
- Parameters:
msg_bytes – the content of the .msg file
Returns: A RFC 822 string
- mailsuite.utils.create_email(message_from: str, message_to: list[str] | None = None, message_cc: list[str] | None = None, subject: str | None = None, message_headers: dict | None = None, attachments: list[tuple[str, bytes]] | None = None, plain_message: str | None = None, html_message: str | None = None) str[source]
Creates an RFC 822 email message and returns it as a string
- Parameters:
message_from – The value of the message from header
message_to – A list of addresses to send mail to
message_cc – A List of addresses to Carbon Copy (CC)
subject – The message subject
message_headers – Custom message headers
attachments – A list of tuples, containing a filename and bytes
plain_message – The plain text message body
html_message – The HTML message body
Returns: A RFC 822 email message
- mailsuite.utils.decode_base64(data: str) bytes[source]
Decodes a base64 string, with padding being optional
- Parameters:
data – A base64 encoded string
Returns: The decoded bytes
- mailsuite.utils.from_trusted_domain(message: str | bytes | dict, trusted_domains: list[str] | str, include_sld: bool = True, allow_multiple_authentication_results: bool = False, use_authentication_results_original: bool = False) bool[source]
Checks if an email is from a trusted domain based on the contents of the
Authentication-ResultsheaderWarning
Authentication results are not verified by this function, so only use it on emails that have been received by trusted mail servers, and not on third-party emails.
Warning
Set
allow_multiple_authentication_resultstoTrueif and only if the receiving mail service splits the results of each authentication method in separateAuthentication-Resultsheaders and always includes DMARC results.Warning
Set
use_authentication_results_originaltoTrueif and only if you use an email security gateway that adds anAuthentication-Results-Originalheader, such as Proofpoint or Cisco IronPort. This does not include API-based email security solutions, such as Abnormal Security.- Parameters:
message – An email
trusted_domains – A list of trusted domains
include_sld – Also return
Trueif the Second-Level Domain (SLD) of an authenticated domain is intrusted_domainsallow_multiple_authentication_results – Allow multiple
Authentication-Resultsheadersuse_authentication_results_original – Use the
Authentication-Results-Originalheader instead of theAuthentication-Resultsheader
- Returns:
Results of the check
- mailsuite.utils.get_filename_safe_string(string: str | None, max_length: int = 146) str[source]
Converts a string to a string that is safe for a filename
- Parameters:
string – A string to make safe for a filename
max_length – Truncate strings longer than this length
Warning
Windows has a 260 character length limit on file paths
Returns: A string safe for a filename
- mailsuite.utils.get_reverse_dns(ip_address: str, cache: ExpiringDict | None = None, nameservers: list[str] | None = None, timeout: float = 2.0) str | None[source]
Resolves an IP address to a hostname using a reverse DNS query
- Parameters:
ip_address – The IP address to resolve
cache – Cache storage
nameservers – A list of one or more nameservers to use
timeout – Sets the DNS query timeout in seconds
Returns: The reverse DNS hostname (if any)
- mailsuite.utils.is_outlook_msg(content: bytes) bool[source]
Checks if the given content is an Outlook msg OLE file
- Parameters:
content – Content to check
Returns: A flag that indicates if a file is an Outlook MSG file
- mailsuite.utils.parse_authentication_results(authentication_results: str | list, from_domain: str | None = None) dict | list[dict][source]
Parses and normalizes an Authentication-Results header value or list of values
- Parameters:
authentication_results – The value of the header or list of values
from_domain – The message From domain
Returns: A parsed header value or list of parsed values
- mailsuite.utils.parse_dkim_signature(dkim_signature: str | dict) dict | list[source]
Parses a DKIM-Signature header value or list of values
- Parameters:
dkim_signature – A DKIM-Signature header value or list of values
Returns: A parsed DKIM-Signature header value or parsed values
- mailsuite.utils.parse_email(data: str | bytes, strip_attachment_payloads: bool = False) dict[source]
A simplified email parser
- Parameters:
data – RFC 822 message string, or Microsoft .msg bytes
strip_attachment_payloads – Remove attachment payloads
Returns: Parsed email data
Note
Attachment dictionaries with binary payloads contain the value
binary: True. Usemailsuite.utils.decode_base64to convert the payload to bytes.
- mailsuite.utils.parse_email_address(email_address: tuple | str) dict[source]
Parse an email address into its components
Addresses that
email.utils.parseaddrcannot handle are split manually and flagged as noncompliant.- Parameters:
email_address – An address string, or a
(display_name, address)tuple as returned byemail.utils.parseaddr- Returns:
An
OrderedDictwith the keysdisplay_name,address,local,domain,sld(the second-level domain), andcompliant(Falsewhen the address had to be parsed by the fallback splitter)
- mailsuite.utils.query_dns(domain: str, record_type: str, cache: ExpiringDict | None = None, nameservers: list[str] | None = None, timeout: float = 2.0)[source]
Queries DNS
- Parameters:
domain – The domain or subdomain to query about
record_type – The record type to query for
cache – Cache storage
nameservers – A list of one or more nameservers to use
timeout – DNS timeout in seconds
- Returns:
A list of answers