Channels¶
A channel is a chat transport plugin. Each channel translates between its native protocol and Maven's internal bus.InboundMessage / bus.OutboundMessage types. All channels run inside the gateway process; you don't need a separate worker.
Comparison¶
| Channel | Inbound | Outbound | Streaming | Files/images | Reactive only | Setup |
|---|---|---|---|---|---|---|
| Telegram | Long polling | Bot API | Yes (drafts + edits) | Photos, docs, voice, audio, video | No | Setup → |
| Feishu (Lark) | Webhook | Lark API | No | Images | No | Setup → |
| WeCom | Webhook | response_url |
No | Images, voice | Yes | Setup → |
| Matrix | /sync |
m.room.message |
No | Text only | No | Setup → |
| WebSocket | whatsmeow | No | Images | No | Setup → | |
| Web UI | WebSocket | WebSocket | Yes | — (browser file uploads not implemented) | No | Setup → |
Reactive only means a channel can only reply within a short-lived response window opened by the user's last inbound. Cron jobs with deliver: true skip reactive-only channels with a logged warning — pick a different channel for proactive delivery.
Capability model¶
The gateway uses these to decide whether to attempt certain operations (e.g. Telegram reactions for inbound feedback, WeCom cron-delivery skip).
Allowlists¶
Every channel supports an allowFrom list. Empty (or omitted) means "allow all". Non-empty restricts inbound by sender identity. The identifier format varies:
- Telegram — numeric user ID (
"123456789"). - Feishu —
open_id("ou_…"). - WeCom —
useridconfigured in WeCom console ("zhangsan"). - Matrix — MXID (
"@alice:example.org"). PlusallowRoomsfor room IDs ("!roomid:example.org"). - WhatsApp — JID (
"8613800138000@s.whatsapp.net"); the matcher tries both raw and:device-suffix-stripped variants. - Web UI — internal
web-<n>ids (rarely useful; usually disable via auth).
Outbound delivery¶
Outbound messages reach a channel through the bus:
sequenceDiagram
participant Pipeline
participant Bus
participant ChMgr as ChannelManager
participant Channel
participant Obs as events.Fanout / HealthReporter
Pipeline->>Bus: PublishOutbound(msg)
Bus->>ChMgr: subscriber callback(msg)
ChMgr->>Channel: Send(ctx, msg)
alt Send returns error
Channel-->>ChMgr: error
ChMgr-->>Bus: WrapDeliveryFailed
Bus-->>Obs: emit EventOutboundDeliveryFailed
Bus-->>Obs: pulse SignalDeliveryFailed
end
The ChannelManager.Apply flow stops removed channels, starts new ones, and re-registers per-channel outbound subscribers. Hot reload re-runs this same path.
Streaming channels¶
A channel that implements channels.StreamChannel opts into token streaming. The pipeline routes SendStream to it directly with the runtime's event channel. See Concepts: Streaming.
Adding a new channel¶
- Create
internal/plugins/channel/<name>/. - Implement
channels.Channel(and optionallyStreamChannel,InboundPreprocessor). - Implement
plugin.ChannelPlugin(Name,Start,Stop,Channels(cfg)). - Add a config struct under
config.ChannelsConfigand validate it inValidate(). - Register the plugin in
internal/gateway/wire.go.
The kernel never imports your package. Composition is the gateway's job.