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
-
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.
-
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?
-
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.
-
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.
-
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.
-
Lifecycle. Onboarding (a user connects a mailbox), token refresh,
credential revocation, and offboarding (tear down a tenant's poll loop and
purge its credentials).
-
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.
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
(
EmailChannelConfig::from_env,crates/khive-channel-email/src/config.rs);one
KHIVE_EMAIL_MAILBOXper running channel.client-credentials flow (
crates/khive-channel-email/src/oauth.rs: endpointlogin.microsoftonline.com, grantclient_credentials, scopeoutlook.office365.com/.default). There is no provider abstraction. Gmailuses different token flows (service-account JWT bearer, or refresh-token
exchange), so it cannot use the OAuth path today — only basic auth.
(
KHIVE_EMAIL_INGEST_NAMESPACE, defaultlocal); 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
Provider abstraction. Introduce a token-provider seam (e.g. a
TokenProvidertrait) 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.rsis provider-specific today? The XOAUTH2 SASL presentation isalready provider-agnostic; only token acquisition differs.
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?
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.sendmust select the correctmailbox/provider per tenant.
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.
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.
Lifecycle. Onboarding (a user connects a mailbox), token refresh,
credential revocation, and offboarding (tear down a tenant's poll loop and
purge its credentials).
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
§5c) is still specified butunimplemented; this design likely supersedes parts of it and needs a new ADR.
crates/khive-channel-email/docs/exchange-online-oauth-setup.mdandcrates/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.