# 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`.

```pycon
>>> 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`](#correspond.channels.mail.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`](#correspond.channels.mail.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`](#correspond.channels.mail.Email)(\*[, imap, smtp, run])   | A mailbox over IMAP and SMTP.   |
|---------------------------------------------------------------------------------|---------------------------------|

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

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A mailbox over IMAP and SMTP.

#### audience(ref, , draft=None)

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`](#correspond.channels.mail.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`](correspond.model.html.md#correspond.model.Audience)

#### *property* capabilities *: [Capabilities](correspond.model.html.md#correspond.model.Capabilities)*

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

#### parse_ref(id)

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

* **Return type:**
  [`ConversationRef`](correspond.model.html.md#correspond.model.ConversationRef)

#### poll(ref, , cursor=None, limit=None)

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

#### prepare(ref, draft)

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

* **Return type:**
  [`Draft`](correspond.model.html.md#correspond.model.Draft)

#### read(ref, , since=None, limit=None)

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

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`Message`](correspond.model.html.md#correspond.model.Message)]

#### send(ref, draft, , dry_run=False)

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`](correspond.model.html.md#correspond.model.SendResult)

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

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

* **Return type:**
  [`SendResult`](correspond.model.html.md#correspond.model.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)

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

* **Return type:**
  [`Authenticity`](correspond.model.html.md#correspond.model.Authenticity)
