Skip to content
/ pymap Public

Lightweight, asynchronous IMAP serving in Python.

License

Notifications You must be signed in to change notification settings

icgood/pymap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pymap

Lightweight, asynchronous IMAP serving in Python.

build PyPI PyPI platforms PyPI

This project attempts to simplify the complexity of the IMAP protocol into a set of clean Python APIs that can be implemented by pluggable backends. Everything runs in an asyncio event loop.

Table of Contents

Install and Usage

$ pip install pymap
$ pymap --help
$ pymap dict --help
$ pymap maildir --help

dict Plugin

The dict plugin uses in-memory dictionary objects to store mail and metadata. While the server is running, all concurrent and future connections will see the same data, including added and removed messages, but no changes will persist if the server is restarted.

You can try out the dict plugin with demo data:

$ pymap --port 1143 --debug dict --demo-data

In another terminal, connect to port 1143 and run some commands:

* OK [CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN AUTH=LOGIN AUTH=CRAM-MD5 BINARY UIDPLUS MULTIAPPEND IDLE APPENDLIMIT=1000000000] Server ready my.hostname.local
. login demouser demopass
. OK Authentication successful.
. select INBOX
* OK [PERMANENTFLAGS (\Answered \Deleted \Draft \Flagged \Seen)] Flags permitted.
* FLAGS (\Answered \Deleted \Draft \Flagged \Recent \Seen)
* 4 EXISTS
* 1 RECENT
* OK [UIDNEXT 105] Predicted next UID.
* OK [UIDVALIDITY 4097774359] UIDs valid.
* OK [UNSEEN 4] First unseen message.
. OK [READ-WRITE] Selected mailbox.
. logout
* BYE Logging out.
. OK Logout successful.

Here are some other commands to try:

. uid fetch 1:* all
. list "" *
. create "A New Folder"
. store * +FLAGS (\Deleted)
. expunge

Add new messages using the append command:

. append INBOX (\Flagged) {38+}
From: [email protected]

test message!

maildir Plugin

The maildir plugin uses on-disk storage for mail and metadata. For mail data, it uses the eponymous Maildir format. However, since Maildir alone is not enough for modern IMAP usage, it is extended with additional data as described in Dovecot's MailboxFormat/Maildir, with the intention of being fully compatible.

For login, the plugin uses files compatible with the /etc/passwd, /etc/shadow, and /etc/group formats. By default, they are prefixed with pymap-etc- and placed inside the configured base directory to avoid conflicting with the corresponding system files.

maildir Quick Start

Start the pymap server pointing to a base directory, e.g.:

$ pymap --port 1143 --debug maildir ~/maildir/

In another terminal, use pymap-admin to create a user to login with:

$ pymap-admin set-user demouser
# Type in a password at the prompt, e.g. "demopass"

If you already have a maildir folder that you could like to reuse, you can re-assign your user's mailbox_path:

$ pymap-admin set-user --param mailbox_path=/path/to/mailbox demouser
# Re-type or change the password

You are now ready to login to IMAP on port 1143 using your favorite mail client.

redis Plugin

The redis plugin uses the Redis data structure store for mail and metadata. It requires redis-py and will not appear in the plugins list without it.

$ pip install 'pymap[redis]'
$ pymap redis --help

Keys are composed of a heirarchy of prefixes separated by /. For example, the key containing the flags of a message might be:

/ns/eacb1cf1558741d0b5419b3f838882f5/mbx/Fdaddd3075d7b42e78a7edb1d87ee5800/msg/9173/flags

In this example, the eacb1cf1558741d0b5419b3f838882f5 and Fdaddd3075d7b42e78a7edb1d87ee5800 prefixes are randomly generated IDs acting as the namespaces for the login user and mailbox, respectively, and the message has UID 9173.

redis Quick Start

Start the pymap server pointing to a local redis server, or use --address to connect to a remote redis, e.g.:

$ pymap --port 1143 --debug redis  # --address redis://my.redis

In another terminal, use pymap-admin to create a user to login with:

$ pymap-admin set-user demouser
# Type in a password at the prompt, e.g. "demopass"

You are now ready to login to IMAP on port 1143 using your favorite mail client.

Admin Tool

With optional dependencies, the pymap server will also open a gRPC service providing administrative operations for the running server.

$ pip install 'pymap[admin,macaroon]'

The admin service can create, update, and delete users, deliver new messages, check credentials, and provide health checks.

The pymap-admin CLI tool simplifies interacting with the admin service. It can also be installed standalone to interact with remote pymap servers:

$ pip install pymap-admin
$ pymap-admin --help

Configuring an MTA

The admin tool can be used as a "local delivery agent" for an MTA with the append sub-command. For example, in postfix you might use:

mailbox_command = /some/where/pymap-admin --from "$SENDER" "$USER"

This setup may be combined with remote authentication to keep a clean separation between your MTA and pymap.

Supported Extensions

In addition to RFC 3501, pymap supports a number of IMAP extensions to give clients easier and more powerful use.

Adds the IDLE capability and command, which lets clients wait (without issuing commands) and receive mailbox updates as they happen without polling.

Defines some conventions for handling multi-access in scenarios such as EXPUNGE and mailbox deletion.

Adds the ID capability and command, which lets clients send and receive arbitrary information for "statistical" purposes and bug reports.

Adds the MULTIAPPEND capability, allowing multiple messages to be atomically appended to a mailbox with a single APPEND command.

Adds the BINARY extension, providing better support for the encoding and transfer of binary data.

Adds the UIDPLUS capability, which adds the UID EXPUNGE command and defines the APPENDUID and COPYUID giving clients more insight into the messages added to a mailbox.

No additional functionality by itself, but allows pymap to be extended easily and more robustly handle bad client implementations.

Adds additional IMAP response codes that can help tell an IMAP client why a command failed.

Adds the APPENDLIMIT= capability, declaring the maximum message size a server will accept from an APPEND command. Mailbox-specific limitations defined by the RFC are not supported.

Adds the OBJECTID capability, assigning unique IDs to mailboxes, messages, and threads to improve client caching and display.

Development and Testing

You will need to do some additional setup to develop and test plugins. Install Hatch to use the CLI examples below.

Run all tests and linters:

$ hatch run check

Type Hinting

This project makes heavy use of Python's type hinting system, with the intention of a clean run of mypy.

No code contribution will be accepted unless it makes every effort to use type hinting to the extent possible and common in the rest of the codebase.