Skip to main content

ADR-008: Channel Service Abstraction

  • Status: Accepted
  • Date: 2026-04-16
  • Author: CTO Agent (KALA-151)

Context

Telegram and WhatsApp channel plugins originally read environment variables directly. That works for one Telegram bot and one WhatsApp number, but it does not scale to multi-tenant or multi-namespace deployments where each department may need a different channel configuration.

The channel layer also needs a stable contract so future web, Slack, email, and database-backed channel records can be added without rewriting every plugin.

Decision

Introduce a ChannelService interface:

export interface ChannelService {
getActiveChannel(type: ChannelType): Promise<ChannelConfig | null>;
listActive(type?: ChannelType): Promise<ChannelConfig[]>;
findByRoutingKey?(key: string): Promise<ChannelConfig | null>;
}

The current EnvChannelService reads the existing config object and returns one active Telegram channel and one active WhatsApp channel. Unsupported types return null or an empty list.

Fastify exposes the service as app.channelService so plugins depend on the interface instead of direct environment reads.

Alternatives Considered

Keep direct env-var reads in plugins

Rejected. It keeps the first deployment simple but makes multi-channel routing and testing harder.

Move immediately to database-backed channels

Rejected for this phase. The product needs the abstraction first; the database model and management UI can follow without changing plugin contracts.

Per-plugin configuration services

Rejected. Telegram and WhatsApp need the same shape: type, id, department, namespace, enabled flag, and config map.

Consequences

Positive

  • Channel plugins consume a stable interface.
  • Tests can instantiate EnvChannelService with a small config stub.
  • Future DB-backed routing can support multiple channels per type.
  • Department and namespace scoping are first-class fields on ChannelConfig.

Negative

  • The env-backed implementation still represents only one channel per type.
  • Secrets remain in process config until a DB-backed secret storage design is added.
  • findByRoutingKey() is optional until inbound routing needs it.

Implementation Notes

  • src/lib/channels/channel-service.ts defines the interface and env-backed implementation.
  • The service currently supports telegram and whatsapp.
  • ChannelType already includes slack, email, and web for future implementations.