correspond.channels.mail#

Email: IMAP to read and listen, SMTP to send, with the standard library.

References

  • email:: the whole folder (INBOX unless configured), which is what listen watches.

  • email:<address>: the correspondence with one address: its messages in the folder, and sends to it.

  • ?folder=<name> after either picks the folder for this reference alone, so one process reads several (Gmail serves labels as folders: email:?folder=Newsletters/AI). ?from=<sender>,<sender> on email: keeps the messages from any of several senders, each an address or a whole domain (@example.org). Both together: email:?folder=News&from=ada@example.org,@example.net. A value is percent-encoded where it has a space, &, ? or % (email:?folder=%5BGmail%5D/All%20Mail), and so is an address with one of those. A folder may be named as written (Énergie) or as the server lists it in modified UTF-7 (&AMk-nergie); both are one reference. On a command line, quote a reference with ? or &: correspond read 'email:?folder=News'.

  • ?gmail=<search> adds Gmail’s own search syntax (X-GM-RAW) on a server that advertises X-GM-EXT-1, and is refused elsewhere: email:?gmail=category:promotions+newer_than:7d. As in Gmail’s own addresses, + is a space (a plus sign is %2B). It narrows the other keys; it replaces none of them, so ?from= still checks each From exactly. Listening delivers a message only if it matches when the listen first passes it: a search whose answer changes later (is:starred, a label added afterwards, is:unread) misses the mail that comes to match after newer mail has moved the cursor on.

A message’s conversation is the correspondence with its sender (email:<address>), in whichever folder it was read; native["folder"] says which.

Message ids are Message-ID headers, angle brackets included. A draft’s reply_to is one, and the send carries In-Reply-To and References. Reading never marks a message seen.

Authenticity is claimed unless the topmost Authentication-Results header was added by a server you trust (trusted_authserv_ids) and records dmarc=pass for the From domain: then it is domain. Only the topmost header counts, even when the trusted server added several: a lower one carrying the same authserv-id may have been written by the sender (not every server strips those), so a result recorded lower down fails safe as claimed.

>>> Email().parse_ref("Ada@Example.org").encoded
'email:ada@example.org'
>>> Email().parse_ref("?from=@Example.NET,ada@example.org&folder=News/AI").encoded
'email:?folder=News/AI&from=@example.net,ada@example.org'

Module Attributes

LIST_LOCAL_PARTS

Words that, as a part of an address's local part (dev-team@), mark it as a possible mailing list.

Functions

authenticity(message, trusted_authserv_ids)

domain only when the topmost Authentication-Results comes from a trusted server and DMARC passed for the From domain.

Classes

Email(*[, imap, smtp, run])

A mailbox over IMAP and SMTP.

class correspond.channels.mail.Email(*, imap=None, smtp=None, run=<function run>)[source]#

Bases: object

A mailbox over IMAP and SMTP.

audience(ref, *, draft=None)[source]#

Who reads an email: its address plus the draft’s cc and bcc, asked of nothing but the config.

Scope named, never complete: any address may be an alias, a shared mailbox or an auto-forward, which nothing here can see. A recipient whose domain is not in own_domains makes it external; a list-shaped address (under lists in the config, or a local part naming a group, see LIST_LOCAL_PARTS) adds a class and list_expansion, and, when every address is in an own domain, leaves external unknown, since a list can have outside members. A Bcc address is a class of its own, so moving it to Cc changes the hash. Every email is pushed to its recipients, can be forwarded, and cannot be recalled.

Return type:

Audience

property capabilities: Capabilities#

Read and listen to a folder per reference; send with threading headers.

parse_ref(id)[source]#

"" for the folder, or an address; either may add ?folder= (and the folder ?from=).

Return type:

ConversationRef

poll(ref, *, cursor=None, limit=None)[source]#

New messages since cursor (<uidvalidity>:<uid>); a first poll, or a changed UIDVALIDITY, looks back a day.

prepare(ref, draft)[source]#

The draft as it will go out: notify addresses checked and put in Cc (out of Bcc). Pure.

Return type:

Draft

read(ref, *, since=None, limit=None)[source]#

Messages in the reference’s folder (from its address or senders, if it names any), oldest first; never marked seen.

Return type:

list[Message]

send(ref, draft, *, dry_run=False)[source]#

Send to ref’s address, with In-Reply-To and References when replying; cc in a header, bcc only on the envelope.

A draft.notify address that is neither the To address nor already in Cc is added to Cc (and so leaves Bcc, which would hide it); the plan lists it under cc_added.

Return type:

SendResult

upload(ref, name, data, *, media_type='application/octet-stream', dry_run=False)[source]#

Email a file to ref’s address: a message with the file attached and its name as the subject.

Return type:

SendResult

correspond.channels.mail.LIST_LOCAL_PARTS = ('list', 'all', 'team', 'dev', 'announce', 'info')#

Words that, as a part of an address’s local part (dev-team@), mark it as a possible mailing list.

correspond.channels.mail.authenticity(message, trusted_authserv_ids)[source]#

domain only when the topmost Authentication-Results comes from a trusted server and DMARC passed for the From domain.

Return type:

Authenticity