Skip to content

Architecture v4.8.0

This document describes the Gmail Secretary architecture including the 3-process deployment model, sync engine implementation, IMAP connection pooling, and the IDLE threading model.

Deployment Architecture (v4.8.0+)

Gmail Secretary runs as three coordinated processes managed by supervisord:

┌─────────────────────────────────────────────────────────────────────────┐
│                        Docker Container (supervisord)                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐    │
│  │  Engine API      │   │  MCP Server      │   │  Web UI          │    │
│  │  Port: 8001      │   │  Port: 8000      │   │  Port: 8080      │    │
│  │  (internal only) │   │  (exposed)       │   │  (exposed)       │    │
│  ├──────────────────┤   ├──────────────────┤   ├──────────────────┤    │
│  │ • IMAP sync      │◄──│ • Tool exposure  │   │ • Human UI       │    │
│  │ • IDLE monitor   │   │ • Read from DB   │   │ • Dashboard      │    │
│  │ • Mutations      │◄──┤ • Proxy writes   │◄──┤ • Settings       │    │
│  │ • DB writes      │   │   to Engine      │   │ • AI chat        │    │
│  │ • OAuth mgmt     │   │ • Bearer auth    │   └──────────────────┘    │
│  └──────────────────┘   └──────────────────┘                            │
│         │                                                                │
│         ▼                                                                │
│  ┌──────────────────────────────────────────┐                           │
│  │  SQLite / PostgreSQL Database            │                           │
│  │  • Email cache (FTS5)                    │                           │
│  │  • Gmail labels (JSONB)                  │                           │
│  │  • Embeddings (pgvector)                 │                           │
│  └──────────────────────────────────────────┘                           │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘


                    ┌──────────────────────┐
                    │   Gmail IMAP/SMTP    │
                    │   Google Calendar    │
                    └──────────────────────┘

Process Responsibilities

ProcessPortExposedPurpose
Engine API8001No (internal)Handles all mutations, IMAP sync, OAuth token management
MCP Server8000YesExposes tools to AI clients (Claude, etc.)
Web UI8080YesHuman web interface for email management

Communication Flow

AI Client → MCP Server (8000) → Engine API (127.0.0.1:8001) → Gmail IMAP
User Browser → Web UI (8080) → Engine API (127.0.0.1:8001) → Gmail IMAP

Environment Variables

The ENGINE_API_URL environment variable configures how MCP Server and Web UI communicate with the Engine:

yaml
environment:
  - ENGINE_API_URL=http://127.0.0.1:8001  # Default

When to override: Multi-container deployments where the engine runs on a separate host.

Why Three Processes?

ConcernEngine APIMCP ServerWeb UI
IMAP ConnectionOwns connection, IDLE loopNever touches IMAPNever touches IMAP
Database WritesAll writes happen hereRead-onlyRead-only
OAuth TokensManages tokens, refreshStatelessStateless
UptimeAlways runningScales with AI requestsAlways running
ScalabilitySingle instance (IMAP limit)Can scale horizontallyCan scale horizontally

supervisord Configuration

ini
[program:engine]
command=uv run python -m workspace_secretary.engine --host 127.0.0.1 --port 8001
autostart=true
autorestart=true

[program:mcp-server]
command=uv run python -m workspace_secretary.server
autostart=true
autorestart=true

[program:web-ui]
command=uv run python -m workspace_secretary.web.main
autostart=true
autorestart=true

Database Layer Architecture (v4.8.0+)

The database layer is organized into modular query modules for maintainability and testability:

workspace_secretary/
├── db/
│   ├── types.py              # DatabaseInterface protocol
│   ├── schema.py             # DDL (CREATE TABLE, indexes)
│   ├── postgres.py           # Connection pooling base class
│   └── queries/              # Shared SQL query modules
│       ├── emails.py         # 22 email operations
│       ├── embeddings.py     # 6 semantic search functions
│       ├── contacts.py       # 12 contact functions
│       ├── calendar.py       # 10 calendar functions
│       ├── preferences.py    # 2 user preference functions
│       └── mutations.py      # 4 mutation journal functions
├── engine/
│   └── database.py           # Delegates to db/queries
└── web/
    └── database.py           # Delegates to db/queries (read-only)

Design Principles

  • Single Source of Truth: All SQL queries live in db/queries/ modules
  • Pure Functions: Query functions accept DatabaseInterface and return data
  • No Duplication: Engine and web both use the same query modules
  • Better Testing: Query functions can be tested independently
  • Clear Boundaries: Engine keeps self-healing logic, queries stay pure

Sync Architecture Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                         asyncio event loop                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  sync_loop()              idle_monitor()           embeddings_loop()     │
│  ───────────              ─────────────            ─────────────────     │
│  1. Initial parallel      Manages thread           Background vector     │
│     sync (all folders)    lifecycle only           generation            │
│  2. Sleep 30 min                                                         │
│  3. Catch-up sync         ┌─────────────┐                                │
│  4. Repeat                │ IDLE Thread │                                │
│         │                 │ ─────────── │                                │
│         │                 │ select_folder                                │
│         ▼                 │ idle_start   │                               │
│  ┌─────────────────┐      │ idle_check   │──► loop.call_soon_threadsafe  │
│  │ ThreadPoolExecutor     │ idle_done    │         │                     │
│  │ (5 workers)     │      └──────────────┘         ▼                     │
│  │                 │                         debounced_sync()            │
│  │ ┌─────────────┐ │                               │                     │
│  │ │ IMAP Pool   │ │                               ▼                     │
│  │ │ Queue(5)    │ │◄────────────────────── sync_emails_parallel()       │
│  │ └─────────────┘ │                                                     │
│  └─────────────────┘                                                     │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Connection Architecture

ConnectionPurposeThreadLifecycle
idle_clientIMAP IDLE for INBOX pushDedicated idle-worker threadStartup → shutdown
Connection Pool (1-5)Parallel folder syncThreadPoolExecutor workersOn-demand, pooled

Sync Strategy

Phase 1: Initial Sync (Startup)

All configured folders sync in parallel using the connection pool:

Folders: [INBOX, Sent, Drafts, [Gmail]/All Mail]
Pool:    [conn1, conn2, conn3, conn4, conn5]

conn1 → INBOX
conn2 → Sent
conn3 → Drafts
conn4 → [Gmail]/All Mail
conn5 → (idle in pool)

Phase 2: Real-time Updates (IDLE)

INBOX monitored via IMAP IDLE on dedicated thread:

  • EXISTS → new email arrived
  • EXPUNGE → email deleted
  • Triggers debounced_sync() via loop.call_soon_threadsafe()

Phase 3: Catch-up Sync (Periodic)

Every 30 minutes (configurable), parallel sync runs again to:

  • Sync non-INBOX folders (Sent, Drafts, labels)
  • Catch missed IDLE notifications (connection drops)
  • Update flags via CONDSTORE/HIGHESTMODSEQ

Configuration

Environment VariableDefaultDescription
MAX_SYNC_CONNECTIONS5Size of IMAP connection pool
SYNC_CATCHUP_INTERVAL1800Catch-up sync interval in seconds (30 min)

Why This Architecture?

Problem: IMAP Blocking Calls

IMAP operations (select_folder, idle_check, fetch) are blocking. Running them on the asyncio event loop freezes all async tasks.

Solution: Dedicated Threads

  1. IDLE Thread: Runs entire IDLE loop (select_folderidle_startidle_checkidle_done) on a dedicated thread. Communicates back via loop.call_soon_threadsafe().

  2. Sync Thread Pool: ThreadPoolExecutor with pooled IMAP connections. Each folder sync runs in its own worker thread. asyncio.gather() coordinates parallel execution.

Result

  • Event loop never blocks
  • Up to 5 folders sync simultaneously
  • IDLE provides instant INBOX updates
  • Catch-up sync handles edge cases

Gmail Connection Limits

Gmail allows up to 15 simultaneous IMAP connections per account. This architecture uses:

  • 1 connection for IDLE
  • Up to 5 connections for sync pool
  • Total: 6 connections (well under limit)

Released under the MIT License.