async-imap

repository·main·Indexed 40 days ago

https://github.com/chatmail/async-imap

An asynchronous Rust implementation of the IMAP protocol (RFC 3501 and extensions) version 0.11.3. It provides a client for connecting to mail servers to list, fetch, search, and manage emails, featuring support for IDLE, OAuth2 authentication via the Authenticator trait, and mailbox operations such as select, store, and expunge.

Tokens
11.8K
Snippets
35
Records
60
Agent score
99%

What's inside async-imap

  1. Overview of async-imap

    main

    async-imap is an asynchronous implementation of the IMAP protocol (RFC 3501 and extensions) for Rust. It allows developers to connect to IMAP servers to list, fetch, search for emails, and monitor mailboxes for changes.

    Key workflow:

    1. Use the connect function to obtain an unauthenticated Client.
    2. Use Client::login (for username/password) or Client::authenticate (for challenge/response) to authenticate.
    3. Authentication returns a Session object, which provides access to the server's mailboxes.
  2. Explore async-imap examples

    main

    The async-imap-examples package provides several implementation patterns for using the IMAP client. You can refer to these examples to understand different integration scenarios:

    • basic: A minimal implementation demonstrating fundamental client usage.
    • idle: Demonstrates how to perform an IMAP IDLE call and how to interrupt that call (e.g., based on user input from stdin).
    • gmail_oauth2: Shows how to authenticate with Gmail using the OAUTH2 mechanism.
    • futures: A variation of the basic example that uses the futures (futures-executor) executor instead of the default runtime.
  3. Set up GreenMail for integration testing

    main

    To run the integration tests included in this project, you must have a running instance of GreenMail. The recommended method is using Docker to pull and run the standalone image with specific options enabled for testing:

    $ docker pull greenmail/standalone:1.5.9
    $ docker run -t -i -e GREENMAIL_OPTS='-Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.auth.disabled -Dgreenmail.verbose' -p 3025:3025 -p 3110:3110 -p 3143:3143 -p 3465:3465 -p 3993:3993 -p 3995:3995 greenmail/standalone:1.5.9
  4. How Client and Session work together

    main

    The async-imap library uses a two-stage lifecycle model:

    1. Client: Represents the raw connection to the IMAP server. It is used for initial connection setup, handling STARTTLS upgrades, and performing authentication.
    2. Session: Represents an authenticated state. Once you call .login() or .authenticate() on a Client, you receive a Session. All mailbox-specific operations (like select, fetch, and search) must be performed on the Session object, not the Client.
  5. Use ImapStream for asynchronous IMAP communication

    main

    The ImapStream struct wraps an underlying asynchronous reader and writer (implementing Read + Write) to handle IMAP protocol communication. It automatically parses incoming data into ResponseData messages and provides methods to encode outgoing Request messages. It implements the Stream trait, allowing you to asynchronously iterate over incoming server responses.

    use async_imap::imap_stream::ImapStream;
    use futures_util::StreamExt;
    
    // Assuming `inner` is a type implementing AsyncRead + AsyncWrite
    let mut imap_stream = ImapStream::new(inner);
    
    while let Some(result) = imap_stream.next().await {
        match result {
            Ok(response) => {
                // Handle ResponseData
            }
            Err(e) => {
                // Handle error
                break;
            }
        }
    }
  6. Understand IMAP capability requirements and behavior

    main

    When working with IMAP capabilities, keep the following protocol rules in mind:

    • Mandatory Capabilities: All servers implement STARTTLS, LOGINDISABLED, and AUTH=PLAIN.
    • LOGINDISABLED: If the server advertises LOGINDISABLED, it means the LOGIN command is disabled. An IMAP client MUST NOT issue the LOGIN command if this capability is present, even if credentials are valid.
    • Client Implementation: Clients should not require any capability other than IMAP4rev1 and MUST ignore any unknown capability names.
    • Authentication: Any capability starting with AUTH= indicates a supported authentication mechanism.
  7. Handle unsolicited server responses in a Session

    main

    According to RFC 3501, IMAP servers can unilaterally send messages to the client (e.g., status changes in a selected mailbox) that are not directly related to the current command.

    In async-imap, these are parsed and sent to the unsolicited_responses channel on the Session object. You should monitor this channel to react to real-time changes like EXISTS, EXPUNGE, or FETCH updates.

  8. Manage IMAP storage quota information with Quota types

    main

    The async-imap crate provides high-level types to represent IMAP storage quota information as defined in RFC 2087. These types allow you to inspect resource usage (like storage size or message counts) and limits for specific mailbox roots.

    Key types include:

    • Quota: Represents the quota information for a specific root, containing a list of QuotaResource objects.
    • QuotaResource: Represents a specific resource (e.g., Storage or Message), including its current usage and limit.
    • QuotaRoot: Represents the relationship between a mailbox and its associated quota roots.
    • QuotaResourceName: An enum identifying the type of resource being measured.
  9. Use the IDLE extension to monitor mailbox changes

    main

    The Handle struct allows a client to block and wait for changes to a remote mailbox using the IMAP IDLE command (RFC 2177).

    Important Lifecycle Note: To prevent being logged off by servers with inactivity timeouts, it is highly recommended to terminate and re-issue the IDLE command at least every 29 minutes. The Handle::wait() method automates this by using a 29-minute timeout.

    Usage Workflow:

    1. Call init() to send the IDLE command and establish the idle state.
    2. Call wait() or wait_with_timeout(duration) to start listening for server responses.
    3. When you are finished, call done() to send the DONE command and recover the underlying Session.

    Constraints:

    • As long as a Handle is active, the mailbox cannot be otherwise accessed.
    • wait() and wait_with_timeout() must be called after init().
    • done() must be called after init().
    // 1. Initialize the idle connection
    handle.init().await?;
    
    // 2. Wait for changes (automatically handles 29-minute re-issue logic)
    let (fut, interrupt) = handle.wait();
    
    // In a real application, you would select on 'fut' and your 'interrupt' token
    match fut.await? {
        IdleResponse::NewData(data) => println!("New data received: {:?}", data),
        IdleResponse::Timeout => println!("Idle timed out"),
        IdleResponse::ManualInterrupt => println!("Idle interrupted manually"),
    }
    
    // 3. Clean up and recover the session
    let session = handle.done().await?;
  10. Authenticate and use an IMAP Session

    main

    Authentication is performed using Client::login (for username/password) or Client::authenticate (for challenge/response). Successful authentication returns a Session object. The Session is the primary interface for interacting with mailboxes, allowing you to select mailboxes, fetch messages, and search for emails.

    // Authenticate to get a Session
    let mut session = client
        .login("alice@example.org", "password")
        .await
        .map_err(|(err, _client)| err)?;
    
    // Select a mailbox
    session.select("INBOX").await?;
    
    // Fetch messages
    let messages_stream = session.fetch("1", "RFC822").await?;
    let messages: Vec<_> = messages_stream.try_collect().await?;
    
    // Logout
    session.logout().await?;
  11. Upgrade a connection using STARTTLS

    main

    If you have an unencrypted connection, you can upgrade it to TLS using the STARTTLS command. You must run the command, extract the underlying stream using into_inner(), wrap it in a TLS library (like async-native-tls or rustls), and then re-wrap it in a new Client instance. Note that there is no server greeting sent after a successful STARTTLS upgrade.

    // Upgrade the connection
    client.run_command_and_check_ok("STARTTLS", None).await?;
    let stream = client.into_inner();
    
    // Convert stream to TLS (using a library like async-native-tls)
    let tls_stream = ...; 
    
    // Wrap back into a Client
    let client = Client::new(tls_stream);
  12. Connect to an IMAP server

    main

    To connect to an IMAP server, pass a stream (e.g., a TCP or TLS stream) to Client::new(). This returns an unauthenticated Client. After creating the client, you must read the server's greeting before proceeding with authentication.

    Note: If connecting via a non-TLS port (like port 143), you may need to upgrade the connection using the STARTTLS command before wrapping the stream in TLS.

    // 1. Create an unauthenticated client
    let mut client = Client::new(stream);
    
    // 2. Read the server greeting
    let _greeting = client
        .read_response().await?
        .expect("unexpected end of stream, expected greeting");