Mailbox Folder Cache & Management

The Squibble Email Gateway supports comprehensive hierarchical folder and directory management for mailboxes. To provide sub-millisecond response times in client sidebars and user interfaces, Squibble utilizes a Hybrid Folder Cache (Option 1) which caches directory trees in PostgreSQL while fetching message payloads statelessly on demand.


1. Architectural Strategy: Option 1 Hybrid Cache

Unlike fully synchronized mailboxes that copy all email bodies and attachments locally (leading to massive database bloat and storage liabilities), Squibble keeps message retrieval completely stateless.

  • Folder Cache: Hierarchical trees and thin directory metadata (such as MESSAGES and UNSEEN status counts, UIDVALIDITY, and UIDNEXT) are cached in PostgreSQL.
  • Message Streaming: Individual email listings and attachments are pulled on-demand directly from the IMAP server and streamed over REST.
  • Performance Impact: Eliminates the slow, blocking 500ms synchronous IMAP round-trips needed to render mail sidebars. Renders navigation interfaces in under 10ms.

2. API Endpoints Reference

All endpoints require a valid JWT bearer token. Scopes are granted per action:

EndpointRequired scope
GET …/folders (list)messages:index
POST …/folders (create)folders:write
DELETE …/folders/{path} (delete)folders:write
PUT …/folders/{path}/subscriptionfolders:write
POST …/folders/{path}/messages/actions (move/copy)messages:move

A read-only (messages:index) token can list folders but cannot create, delete, subscribe, or move/expunge messages.

A. List Folders

Retrieve the cached folder hierarchy for the mailbox.

  • Endpoint: GET /api/v1/email/account/{mailbox}/folders
  • Query Parameters:
    • subscribed_only (bool, default true): Only return folders marked subscribed.
    • force_refresh (bool, default false): Perform an inline real-time IMAP sync before returning results.
  • Example Response:
    {
      "data": [
        {
          "id": "c1a60fa1-3bc9-4b6b-88a2-f67381180abc",
          "mailbox_id": "8fa80291-7f9a-4c2c-982d-8b0ab971bc3d",
          "path": "INBOX/Invoices",
          "name": "Invoices",
          "delimiter": "/",
          "flags": ["\\HasNoChildren", "\\Unmarked"],
          "special_use": "archive",
          "is_subscribed": true,
          "messages_count": 42,
          "unseen_count": 3,
          "uid_validity": 1717012345,
          "uid_next": 105,
          "highest_mod_seq": 54321,
          "created_at": "2026-05-31T10:00:00.000000Z",
          "updated_at": "2026-05-31T10:05:00.000000Z"
        }
      ]
    }

B. Create Folder

Create a new directory structure on both the IMAP server and local database cache.

  • Endpoint: POST /api/v1/email/account/{mailbox}/folders
  • Payload:
    {
      "path": "INBOX/Receipts/2026"
    }
  • Example Response (201 Created):
    {
      "id": "d2b71ab2-4cd8-5c7c-99b3-07cb1231a4df",
      "mailbox_id": "8fa80291-7f9a-4c2c-982d-8b0ab971bc3d",
      "path": "INBOX/Receipts/2026",
      "name": "2026",
      "delimiter": "/",
      "flags": ["\\HasNoChildren"],
      "is_subscribed": true,
      "messages_count": 0,
      "unseen_count": 0,
      "created_at": "2026-05-31T10:10:00.000000Z",
      "updated_at": "2026-05-31T10:10:00.000000Z"
    }

C. Delete Folder

Delete a folder structure on the IMAP server and remove it from the local cache.

  • Endpoint: DELETE /api/v1/email/account/{mailbox}/folders/{folder_path:path}
  • Response: 204 No Content

D. Toggle Subscription

Subscribe or unsubscribe from folder tree updates.

  • Endpoint: PUT /api/v1/email/account/{mailbox}/folders/{folder_path:path}/subscription
  • Payload:
    {
      "subscribed": false
    }
  • Response (200 OK): Returns the updated FolderEntry with is_subscribed: false.

E. Bulk Message Actions (Move / Copy)

Perform bulk inter-folder operations cleanly. Translates into atomized server-side IMAP operations.

  • Endpoint: POST /api/v1/email/account/{mailbox}/folders/{folder_path:path}/messages/actions
  • Payload:
    {
      "action": "move",  // or "copy"
      "target_folder": "Archive/Processed",
      "uids": [101, 102, 103]
    }
  • Capability Detection & Fallback:
    • If the upstream IMAP server supports the MOVE capability (RFC 6851), the action executes atomically in one server command: UID MOVE {uids} {target_folder}.
    • On older servers lacking MOVE support, Squibble transparently falls back to an emulated copy sequence: UID COPY followed by marking messages as \Deleted and performing EXPUNGE.

3. Command Injection Security

To prevent IMAP command injection (such as injecting CR/LF characters into mailbox path parameters), strict route and schema regex validation is enforced. All paths are validated against the following strict alphanumeric and delimiter constraint before executing commands:

pattern=r"^[a-zA-Z0-9_\-\./]+$"

Carriage returns, line feeds, and control characters are rejected immediately with a 422 Unprocessable Entity response, safeguarding credentials and server state.


4. Starlette Thread Pool Execution

All folder operations executing synchronous imaplib blocking calls are declared as synchronous def endpoints. This allows Starlette to process them on its background worker threadpool, preventing high-latency IMAP network I/O from blocking the asyncio main event loop.

Sourced from docs/features/08_mailbox_folder_cache_and_management in the repo. Edits go through the same review as code.