PSA/ee/docs/plans/2025-12-26-imap-inbound-email-service-plan.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

7.7 KiB

IMAP Inbound Email Service Plan

Status: Draft Owner: Email Platform Last updated: 2025-12-26

Goals

  • Add an IMAP inbound email provider that feeds the same INBOUND_EMAIL_RECEIVED event flow as Gmail and Microsoft.
  • Provide a resilient, long-running IMAP service that uses IDLE, reconnects automatically, and handles multiple folders per mailbox.
  • Expose IMAP as a first-class inbound email provider in the settings UI alongside Google and Microsoft.

Current State (Research Summary)

  • Gmail inbound flow: server/src/app/api/email/webhooks/google/route.ts decodes Pub/Sub, loads provider config, fetches full message details via GmailAdapter, and publishes INBOUND_EMAIL_RECEIVED via shared/events/publisher.ts.
  • Microsoft inbound flow: server/src/app/api/email/webhooks/microsoft/route.ts validates Graph notifications, fetches message details via MicrosoftGraphAdapter, then publishes INBOUND_EMAIL_RECEIVED (with fallback minimal payload on fetch failure).
  • Test flow: server/src/services/email/MailHogPollingService.ts emits INBOUND_EMAIL_RECEIVED directly via the server EventBus.
  • Workflow trigger: services/workflow-worker/src/WorkflowWorker.ts consumes workflow events (Redis stream) and starts shared/workflow/workflows/system-email-processing-workflow.ts, which waits for the INBOUND_EMAIL_RECEIVED payload defined in shared/workflow/streams/eventBusSchema.ts.

Proposed Architecture

High-level flow

  1. IMAP service connects to configured mailboxes and folders.
  2. IDLE receives new mail notifications (or polling fallback if IDLE unsupported).
  3. IMAP service fetches the full RFC822/body + headers for new messages.
  4. Parse MIME into EmailMessageDetails and publish INBOUND_EMAIL_RECEIVED using shared/events/publisher.ts.
  5. Workflow worker consumes and runs the existing system email processing workflow (no change).

Service placement

  • New service in services/email-service (Node + TS, similar to services/workflow-worker).
  • Uses shared DB access and shared event publisher to avoid duplicating queue/event logic.
  • Deploy as a long-running worker alongside existing services (server, workflow-worker, temporal-worker).

Data Model & Configuration

Provider types

  • Extend all inbound email unions to include imap:
    • shared/interfaces/inbound-email.interfaces.ts
    • server/src/interfaces/email.interfaces.ts
    • server/src/components/EmailProviderConfiguration.tsx (providerType union)
    • Any API/action payloads that validate provider type

New vendor config table

Create imap_email_provider_config with fields (initial draft):

  • email_provider_id, tenant
  • Connection: host, port, secure (TLS), allow_starttls, auth_type (password | oauth2)
  • Auth: username, password (store encrypted or via tenant secret provider), oauth_access_token, oauth_refresh_token, oauth_expires_at
  • Processing: auto_process_emails, max_emails_per_sync, folder_filters (jsonb array)
  • State: last_uid, uid_validity, last_seen_at, last_error, last_sync_at

Provider CRUD + validation

  • Update server/src/services/email/EmailProviderService.ts and server/src/lib/actions/email-actions/emailProviderActions.ts to support IMAP configs.
  • Add validation in server/src/services/email/EmailProviderValidator.ts for IMAP host/port/auth requirements.
  • Add secret storage integration via getSecretProviderInstance() for IMAP passwords (and optional OAuth tokens) instead of plain DB storage.

IMAP Service Design

Connection lifecycle

  • Maintain one IMAP connection per provider + folder (or multiplex folders on a single connection if library allows).
  • On startup, load active IMAP providers and connect to all configured folders.
  • Use IDLE to listen for EXISTS/RECENT events; on disconnect or IDLE timeout, re-enter IDLE.
  • Implement exponential backoff with jitter on reconnect.

Message tracking + dedupe

  • Track uidvalidity and last_uid per provider/folder; on mismatch, resync (search unseen from scratch).
  • Persist state to imap_email_provider_config to survive restarts.
  • Use Message-ID header plus providerId as a secondary dedupe key (insert into email_processed_messages or a new imap_processed_messages table) to avoid double-publishing.

MIME parsing

  • Fetch full RFC822 or BODY.PEEK[] and parse with a MIME parser (e.g., mailparser).
  • Map into the existing EmailMessageDetails structure:
    • subject, from, to, cc, receivedAt
    • body.text and body.html
    • attachments metadata (id, name, contentType, size)
    • threadId, references, inReplyTo, and headers

Event publishing

  • Publish INBOUND_EMAIL_RECEIVED using shared/events/publisher.ts with payload:
    • tenantId, tenant, providerId, and the full emailData object
  • Reuse existing workflow pipeline and ticket creation logic without changes.

UI/UX Updates (Inbound Email Settings)

  • Add an IMAP card to server/src/components/EmailProviderSelector.tsx and ee/server/src/components/EmailProviderSelector.tsx.
  • Add an IMAP provider form in both OSS and EE components:
    • Fields: mailbox, host, port, TLS/starttls, username, password/app-password, folder filters, auto-process, max emails per sync, inbound ticket defaults.
  • Update provider list cards to show IMAP status and connection errors.

API + Service Integration

  • New IMAP service reads configs via DB (not via server API) to avoid API coupling.
  • Optional admin endpoints (future): manual reconnect, force resync, or pause provider.
  • Add Docker Compose entry for email-service (dev + prod) with required env vars.

Observability

  • Log structured events per provider: connect, disconnect, idle start, new message count, publish success/failure.
  • Update email_providers.status, error_message, last_sync_at from the IMAP service on state changes.
  • Add a lightweight health check endpoint or readiness log for monitoring.

Testing Strategy

  • Unit tests for IMAP MIME parsing into EmailMessageDetails.
  • Integration tests with a local IMAP server (e.g., dockerized test server) validating:
    • IDLE + reconnect
    • Folder filters
    • Deduplication by UID + Message-ID
    • Published INBOUND_EMAIL_RECEIVED payload
  • E2E: simulate incoming email and verify ticket creation via workflow.

Implementation Plan (Tasks)

  • Add imap provider type across shared/server interfaces and validation.
  • Create imap_email_provider_config migration + indices + Citus distribution.
  • Extend provider CRUD/actions to read/write IMAP config and secrets.
  • Build IMAP adapter/parser (message fetch + MIME parsing) and map to EmailMessageDetails.
  • Build services/email-service with connection manager, IDLE loop, reconnect strategy, and publisher.
  • Add UI forms and selector card for IMAP in both OSS and EE bundles.
  • Wire Docker Compose for the new service (dev + prod) and document env vars.
  • Add tests (unit + integration) and update inbound email docs to include IMAP.

Risks & Mitigations

  • IMAP server variance: IDLE support and folder semantics differ. Mitigate with polling fallback and robust folder selection logic.
  • Duplicate events: IMAP can replay on reconnect. Mitigate with UID + Message-ID dedupe and persisted state.
  • Credential handling: Storing passwords requires secure storage; use tenant secret provider and avoid logging secrets.
  • Large attachments: IMAP fetch size can be heavy; fetch metadata first and defer content where possible.

Open Questions

  • Do we want OAuth2 for IMAP (XOAUTH2) in v1 or password-only?
  • Should IMAP providers share the same inbound defaults schema as Google/Microsoft (yes), or add IMAP-specific defaults?
  • Preferred IMAP test server for CI (e.g., Dovecot, GreenMail, or MailHog alternative)?