# 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)?