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
EnvChannelServicewith 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.tsdefines the interface and env-backed implementation.- The service currently supports
telegramandwhatsapp. ChannelTypealready includesslack,email, andwebfor future implementations.