Skip to content
This page was generated and translated with the assistance of AI. If you spot any inaccuracies, feel free to help improve it. Edit on GitHub

IMAP Configuration

PRX-Email connects to IMAP servers over TLS using the rustls library. It supports password authentication and XOAUTH2 for Gmail and Outlook. Inbox sync is UID-based and incremental, with cursor persistence in the SQLite database.

Basic IMAP Setup

rust
use prx_email::plugin::{ImapConfig, AuthConfig};

let imap = ImapConfig {
    host: "imap.example.com".to_string(),
    port: 993,
    user: "[email protected]".to_string(),
    auth: AuthConfig {
        password: Some("your-app-password".to_string()),
        oauth_token: None,
    },
};

Configuration Fields

FieldTypeRequiredDescription
hostStringYesIMAP server hostname (must not be empty)
portu16YesIMAP server port (typically 993 for TLS)
userStringYesIMAP username (usually the email address)
auth.passwordOption<String>One ofApp password for IMAP LOGIN
auth.oauth_tokenOption<String>One ofOAuth access token for XOAUTH2

Authentication

Exactly one of password or oauth_token must be set. Setting both or neither will result in a validation error.

Common Provider Settings

ProviderHostPortAuth Method
Gmailimap.gmail.com993App password or XOAUTH2
Outlook / Office 365outlook.office365.com993XOAUTH2 (recommended)
Yahooimap.mail.yahoo.com993App password
Fastmailimap.fastmail.com993App password
ProtonMail Bridge127.0.0.11143Bridge password

Syncing the Inbox

The sync method connects to the IMAP server, selects a folder, fetches new messages by UID, and stores them in SQLite:

rust
use prx_email::plugin::SyncRequest;

plugin.sync(SyncRequest {
    account_id: 1,
    folder: Some("INBOX".to_string()),
    cursor: None,        // Resume from last saved cursor
    now_ts: now,
    max_messages: 100,   // Fetch at most 100 messages per sync
})?;

Sync Flow

mermaid
sequenceDiagram
    participant Plugin as EmailPlugin
    participant DB as SQLite
    participant IMAP as IMAP Server

    Plugin->>DB: Load sync cursor for account/folder
    Plugin->>IMAP: TLS Connect + Login/XOAUTH2
    Plugin->>IMAP: SELECT folder
    Plugin->>IMAP: UID SEARCH (from cursor+1)
    IMAP-->>Plugin: UID list
    loop Each UID
        Plugin->>IMAP: UID FETCH (RFC822)
        IMAP-->>Plugin: Raw message
        Plugin->>Plugin: Parse MIME (headers, body, attachments)
        Plugin->>DB: UPSERT message
    end
    Plugin->>DB: Update sync cursor
    Plugin->>IMAP: LOGOUT

Incremental Sync

PRX-Email uses UID-based cursors to avoid re-fetching messages. After each sync:

  1. The highest UID seen is saved as the cursor
  2. The next sync starts from cursor + 1
  3. Messages with existing (account_id, message_id) pairs are updated (UPSERT)

The cursor is stored in the sync_state table with the compound key (account_id, folder_id).

Multi-Folder Sync

Sync multiple folders for the same account:

rust
for folder in &["INBOX", "Sent", "Drafts", "Archive"] {
    plugin.sync(SyncRequest {
        account_id,
        folder: Some(folder.to_string()),
        cursor: None,
        now_ts: now,
        max_messages: 100,
    })?;
}

Sync Scheduler

For periodic syncing, use the built-in sync runner:

rust
use prx_email::plugin::{SyncJob, SyncRunnerConfig};

let jobs = vec![
    SyncJob { account_id: 1, folder: "INBOX".into(), max_messages: 100 },
    SyncJob { account_id: 1, folder: "Sent".into(), max_messages: 50 },
    SyncJob { account_id: 2, folder: "INBOX".into(), max_messages: 100 },
];

let config = SyncRunnerConfig {
    max_concurrency: 4,         // Max jobs per runner tick
    base_backoff_seconds: 10,   // Initial backoff on failure
    max_backoff_seconds: 300,   // Maximum backoff (5 minutes)
};

let report = plugin.run_sync_runner(&jobs, now, &config);
println!(
    "Run {}: attempted={}, succeeded={}, failed={}",
    report.run_id, report.attempted, report.succeeded, report.failed
);

Scheduler Behavior

  • Concurrency cap: At most max_concurrency jobs run per tick
  • Failure backoff: Exponential backoff with base * 2^failures formula, capped at max_backoff_seconds
  • Due check: Jobs are skipped if their backoff window has not elapsed
  • State tracking: Per account::folder key, tracks (next_allowed_at, failure_count)

Message Parsing

Incoming messages are parsed using the mail-parser crate with the following extraction:

FieldSourceNotes
message_idMessage-ID headerFalls back to SHA-256 of raw bytes
subjectSubject header
senderFirst address from From header
recipientsAll addresses from To headerComma-separated
body_textFirst text/plain part
body_htmlFirst text/html partFallback: raw section extraction
snippetFirst 120 chars of body_text or body_html
references_headerReferences headerFor threading
attachmentsMIME attachment partsJSON-serialized metadata

TLS

All IMAP connections use TLS via rustls with the webpki-roots certificate bundle. There is no option to disable TLS or use STARTTLS -- connections are always encrypted from the start.

Next Steps

Released under the Apache-2.0 License.