Skip to content

Email channel: multi-tenant, multi-provider design #371

Description

@ohdearquant

Summary

The email channel (ADR-056) is currently single-mailbox and single-provider. To
support a hosted "email your agent" service — and even to add a second provider
(Gmail) — the channel needs a multi-tenant, multi-provider design. This issue
collects the design questions before any ADR or implementation.

Current state

  • One mailbox per process. Configuration comes from environment variables
    (EmailChannelConfig::from_env, crates/khive-channel-email/src/config.rs);
    one KHIVE_EMAIL_MAILBOX per running channel.
  • Single OAuth provider. Token acquisition is hardwired to Microsoft's
    client-credentials flow (crates/khive-channel-email/src/oauth.rs: endpoint
    login.microsoftonline.com, grant client_credentials, scope
    outlook.office365.com/.default). There is no provider abstraction. Gmail
    uses different token flows (service-account JWT bearer, or refresh-token
    exchange), so it cannot use the OAuth path today — only basic auth.
  • Single inbound namespace. Ingested mail is written under one namespace
    (KHIVE_EMAIL_INGEST_NAMESPACE, default local); there is one poll loop.

Goal

Many independent users, each connecting their own mailbox (their own Microsoft
365 tenant, Google Workspace domain, or generic IMAP/SMTP account), served by a
shared channel with per-tenant isolation.

Design areas / open questions

  1. Provider abstraction. Introduce a token-provider seam (e.g. a
    TokenProvider trait) with implementations for: Microsoft client-credentials,
    Google service-account domain-wide delegation, refresh-token exchange
    (Google/Microsoft), and basic-auth/no-OAuth. Where does it plug in, given
    oauth.rs is provider-specific today? The XOAUTH2 SASL presentation is
    already provider-agnostic; only token acquisition differs.

  2. Per-tenant credentials and secret storage. Env config is single-tenant.
    A multi-tenant deployment needs a per-tenant credential store, encrypted at
    rest, keyed by tenant. Secrets must stay out of the KG and any content store
    (existing rule). How are credentials provisioned, rotated, and revoked?

  3. Routing and isolation. Inbound mail for tenant A must land in tenant A's
    namespace and never be visible to tenant B. How does a polled message resolve
    to a tenant and namespace? Outbound comm.send must select the correct
    mailbox/provider per tenant.

  4. Inbound poll fan-out. One poll loop per mailbox does not scale to N
    mailboxes. Design a scheduler/pool with per-tenant backoff and rate limits,
    and a strategy for connection reuse vs. per-tenant connections.

  5. Authorization. Per-tenant actor identity at the Gate (ADR-018), aligned
    with namespace-as-attribution (ADR-007). Confirm the Gate remains the single
    authorization seam; no handler-level or storage-level tenant checks.

  6. Lifecycle. Onboarding (a user connects a mailbox), token refresh,
    credential revocation, and offboarding (tear down a tenant's poll loop and
    purge its credentials).

  7. Guardrail per tenant. Inbound external content is data, never
    instructions. Confirm this structural invariant holds per tenant, and where a
    content-classification seam would sit on inbound and outbound paths.

Related

  • ADR-056 (channel transport layer) — outbound (§5c) is still specified but
    unimplemented; this design likely supersedes parts of it and needs a new ADR.
  • Setup guides added for the two concrete providers:
    crates/khive-channel-email/docs/exchange-online-oauth-setup.md and
    crates/khive-channel-email/docs/gmail-setup.md.

This is a design-discussion issue. The outcome should be one or more ADRs, not a
direct PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    channelExternal messaging channel layer (email/whatsapp/telegram) for agent<->maintainer commsenhancementNew feature or requestneeds-design

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions