Skip to main content

Milestone 5: Inbox management

Purpose: Admins can create and manage inboxes, connect via IMAP or OAuth (Gmail/Microsoft), store credentials encrypted, and see connection status. No background fetch or ticket creation yet.

Exit state: Inbox CRUD; connect UI (IMAP form + test, Gmail/Microsoft OAuth); credentials encrypted; connection status and reconnect CTA in UI.

Spec reference: §2.2 Inbox Management (all subsections), §5.4 Inbox, §5.21b InboxAlias, §6.3 Inbox connect wizard, §6.10 settings/inboxes, §7.1 Credential storage, §10.1 Inboxes.

Prerequisites: M04 (Onboarding and tenant settings). Subscription/trial and inbox limit (M10) can be stubbed: e.g. allow up to 10 inboxes for any tenant in M05.


5.1 Inbox CRUD

Tasks

  1. List inboxes

    • tRPC: list inboxes for tenant (exclude deleted_at). Return id, name, email_address, connection_type, last_error, connected_at, archived_at.
    • Settings → Inboxes: table or list; each row: name, email, connection type icon, status badge (connected / disconnected / error), actions (Settings, Disconnect, Archive).
    • Archived section: collapsed by default; list archived inboxes; optional restore (if plan limit allows).
  2. Create inbox

    • "Add inbox" → wizard or modal: step 1 choose connection type (IMAP, Gmail, Microsoft); step 2 connection details (see 5.2); step 3 name and optional display name. Create inbox row (connected_at = null until connection succeeds).
    • Enforce inbox limit: before create, count active (non-archived, non-deleted) inboxes; if >= plan limit, block with message "Upgrade to add more inboxes" (plan limit from subscription/trial — stub to 10 in M05 if M10 not done).
  3. Update inbox

    • Settings → Inboxes → [id]: edit name, display_name; connection re-auth or credential update (see 5.2). Save via tRPC.
  4. Archive / delete

    • Archive: set archived_at; hide from default list; still count toward plan per spec (or document "archived not counted" per product decision).
    • Disconnect: clear credentials_encrypted, set connected_at = null, last_error; keep inbox row. Stops sync once worker exists (M06).
    • Delete: soft delete (deleted_at) or hard delete with transfer/close open tickets per Section 10.1. Implement at least soft delete and show open ticket count + "Transfer to" or "Close all" before confirm.

Acceptance criteria

CriterionStatus
Admin sees list of inboxes; can add inbox (wizard or flow); can edit and archive/disconnect/delete.
Inbox limit enforced when creating (stub limit or from M10).
Delete flow: confirm with ticket transfer or close option per Section 10.1.

5.2 Connect inbox — IMAP/SMTP

Tasks

  1. IMAP/SMTP form

    • Fields: IMAP host, port, security (TLS/STARTTLS/none), username, password; SMTP host, port, security, username, password. Optional: "Test connection" before save.
  2. Test connection

    • Server-side: use imapflow (or similar) to connect and optionally fetch capability; use nodemailer to send test email or verify SMTP. On success return success; on failure return error message. Do not store credentials until user explicitly saves.
  3. Save and encrypt

    • On save: encrypt credentials (JSON or blob: IMAP + SMTP settings) with AES-256 using ENCRYPTION_KEY from env. Store in inboxes.credentials_encrypted. Set connected_at = now(), clear last_error. Section 7.1.
  4. Encryption implementation

    • packages/email (or packages/config): encrypt(plaintext) and decrypt(ciphertext) using key from env; use IV and auth tag (e.g. AES-256-GCM). Document key format (e.g. base64 32-byte).

Acceptance criteria

CriterionStatus
User can enter IMAP/SMTP details and click "Test connection"; success or error shown.
On save, credentials are encrypted and stored; inbox shows as connected.
Credentials never logged or sent to client; only encrypted blob in DB.

5.3 Connect inbox — Gmail and Microsoft

Tasks

  1. OAuth flows

    • Gmail: Google OAuth2 with scopes for Gmail read/send (e.g. gmail.readonly, gmail.send, or full gmail.modify). Redirect URI from env; callback stores tokens.
    • Microsoft: Microsoft Graph OAuth2 with Mail.Read, Mail.Send (or equivalent). Redirect URI from env; callback stores tokens.
    • Callback: exchange code for tokens; encrypt and store in inbox row (same credentials_encrypted or separate token column); set connected_at, clear last_error.
  2. Token storage

    • Encrypt refresh_token and access_token (and expiry) with same ENCRYPTION_KEY; store in DB. Section 7.1.
  3. Connect UI

    • In inbox wizard or settings: "Connect with Google" / "Connect with Microsoft" buttons; redirect to provider; return to callback then redirect to inbox settings or onboarding next step.
    • If tenant already has inbox and is reconnecting: same flow; update existing inbox row.
  4. Reconnect

    • When last_error = 'oauth_refresh_failed' or connected_at is null, show "Reconnect" CTA; same OAuth flow; on success update tokens and connected_at.

Acceptance criteria

CriterionStatus
User can connect Gmail inbox via OAuth; tokens stored encrypted; inbox shows connected.
User can connect Microsoft inbox via OAuth; tokens stored encrypted; inbox shows connected.
Reconnect flow updates tokens and clears error state.

5.4 Connection status and health

Tasks

  1. Status in UI

    • Inbox list and inbox detail: show status badge — Connected (green), Disconnected (grey), Error (red). If last_error set, show short message and "Reconnect" or "Fix settings".
    • connected_at: display "Connected since <date>" or "Never connected".
  2. Worker updates status (M06)

    • In M06, worker will set last_error and clear connected_at on auth failure. For M05, manual disconnect or failed test can set last_error for testing UI.
  3. Inbox settings page

    • Tab or section: General (name, display_name), Connection (re-auth or edit IMAP), Auto-responses (toggle + body text — optional for M05), Aliases (optional — deferred to M17 §17.4 or post-launch backlog; Section 2.2, 5.21b).

Acceptance criteria

CriterionStatus
Connection status visible in list and in inbox settings; Reconnect CTA when disconnected or error.
Auto-response toggle and body can be saved (used in M06 when creating ticket); aliases can be deferred.

5.5 Key rotation and documentation

Tasks

  1. Document key rotation
    • Runbook: docs/runbooks/key-rotation.md — steps to rotate ENCRYPTION_KEY (decrypt all credentials/tokens with old key, re-encrypt with new key, deploy new key) and AUTH_SECRET (invalidates all sessions). Section 7.1.

Acceptance criteria

CriterionStatus
Key rotation procedure documented; no code change required for M05 beyond doc.

Milestone 5 sign-off

CriterionStatus
All tasks in 5.1–5.5 complete.
All acceptance criteria met.
Admin can create inboxes, connect via IMAP or OAuth, see status, and reconnect; credentials encrypted.
E2E: Inbox create/connect/list/status (see INDEX — Testing strategy).
Ready for M06 (Email pipeline and worker).