> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt
> Use this file to discover all available pages before exploring further.

# Channels Overview

> Messaging platform integrations for NullClaw

Channels connect NullClaw to messaging platforms. Each channel implements the `Channel` interface using vtable-based polymorphism for runtime dispatch.

## Supported Channels

NullClaw supports 18+ messaging platforms:

* **CLI** — Built-in stdin/stdout interface
* **Telegram** — Long-polling bot API
* **Discord** — WebSocket gateway
* **Slack** — Socket mode + HTTP events
* **WhatsApp** — Webhook-based integration
* **Matrix** — Long-polling /sync API
* **Mattermost** — WebSocket + REST API
* **IRC** — TLS socket connection
* **iMessage** — AppleScript + SQLite (macOS only)
* **Email** — IMAP/SMTP protocols
* **Lark/Feishu** — HTTP callback
* **DingTalk** — WebSocket stream mode
* **Signal** — signal-cli JSON-RPC + SSE
* **Nostr** — Decentralized relay protocol
* **LINE** — Messaging API
* **OneBot** — QQ bot protocol
* **QQ** — Native QQ integration
* **MaiXCam** — Hardware device messaging
* **Web** — HTTP/WebSocket gateway

## Channel Interface

All channels implement the `Channel` vtable interface defined in `src/channels/root.zig`:

```zig theme={null}
pub const Channel = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        start: *const fn (ptr: *anyopaque) anyerror!void,
        stop: *const fn (ptr: *anyopaque) void,
        send: *const fn (ptr: *anyopaque, target: []const u8, message: []const u8, media: []const []const u8) anyerror!void,
        name: *const fn (ptr: *anyopaque) []const u8,
        healthCheck: *const fn (ptr: *anyopaque) bool,
        sendEvent: ?*const fn (ptr: *anyopaque, target: []const u8, message: []const u8, media: []const []const u8, stage: OutboundStage) anyerror!void,
        startTyping: *const fn (ptr: *anyopaque, recipient: []const u8) anyerror!void,
        stopTyping: *const fn (ptr: *anyopaque, recipient: []const u8) anyerror!void,
    };
};
```

### VTable Methods

<ParamField path="start" type="function">
  Start the channel (connect, begin listening for messages)
</ParamField>

<ParamField path="stop" type="function">
  Stop the channel (disconnect, clean up resources)
</ParamField>

<ParamField path="send" type="function">
  Send a message to a target (user, channel, room, etc.)
</ParamField>

<ParamField path="name" type="function">
  Return the channel name (e.g., "telegram", "discord")
</ParamField>

<ParamField path="healthCheck" type="function">
  Health check — return true if the channel is operational
</ParamField>

<ParamField path="sendEvent" type="function" default="optional">
  Optional staged outbound event delivery (chunk/final). If null, runtime falls back to `send()` for `.final` and ignores `.chunk`
</ParamField>

<ParamField path="startTyping" type="function" default="no-op">
  Start processing indicator for a recipient (e.g., typing status)
</ParamField>

<ParamField path="stopTyping" type="function" default="no-op">
  Stop processing indicator for a recipient
</ParamField>

## Channel Messages

Channels emit and receive `ChannelMessage` structs:

```zig theme={null}
pub const ChannelMessage = struct {
    id: []const u8,
    sender: []const u8,
    content: []const u8,
    channel: []const u8,
    timestamp: u64,
    reply_target: ?[]const u8 = null,
    message_id: ?i64 = null,
    first_name: ?[]const u8 = null,
    is_group: bool = false,
    sender_uuid: ?[]const u8 = null,
    group_id: ?[]const u8 = null,
};
```

## Configuration Pattern

Channels are configured using the `accounts` pattern in `config.json`:

```json theme={null}
{
  "channels": {
    "telegram": {
      "accounts": {
        "main": {
          "bot_token": "YOUR_BOT_TOKEN",
          "allow_from": ["user_id_1", "user_id_2"]
        },
        "work": {
          "bot_token": "WORK_BOT_TOKEN",
          "allow_from": ["team_member_1"]
        }
      }
    }
  }
}
```

This allows multiple accounts per channel type, each with independent configuration.

## Permission Policies

Channels support fine-grained permission policies:

### DM Policy

<ParamField path="dm" type="enum" default="allow">
  Direct message permission policy:

  * `allow` — Allow all DMs
  * `deny` — Deny all DMs
  * `allowlist` — Only allow DMs from senders in the allowlist
</ParamField>

### Group Policy

<ParamField path="group" type="enum" default="open">
  Group/channel message permission policy:

  * `open` — Allow all group messages
  * `mention_only` — Only respond when explicitly mentioned
  * `allowlist` — Only allow messages from senders in the allowlist
</ParamField>

### Allowlist

<ParamField path="allowlist" type="string[]" default="[]">
  List of allowed sender identifiers. Supports `"*"` wildcard for allow-all. Case-insensitive matching.
</ParamField>

## Message Splitting

Channels automatically split long messages at platform limits while respecting UTF-8 character boundaries:

```zig theme={null}
pub fn splitMessage(msg: []const u8, max_bytes: usize) SplitIterator
```

Examples:

* **Telegram**: 4096 bytes
* **Discord**: 2000 bytes
* **IRC**: 512 bytes (minus prefix overhead)

## Next Steps

<CardGroup cols={2}>
  <Card title="Telegram" icon="telegram" href="/channels/telegram">
    Long-polling bot with media support
  </Card>

  <Card title="Discord" icon="discord" href="/channels/discord">
    WebSocket gateway integration
  </Card>

  <Card title="Slack" icon="slack" href="/channels/slack">
    Socket mode and HTTP events
  </Card>

  <Card title="Signal" icon="signal" href="/channels/signal">
    Private messaging via signal-cli
  </Card>

  <Card title="Nostr" icon="square-rss" href="/channels/nostr">
    Decentralized protocol support
  </Card>

  <Card title="IRC" icon="comments" href="/channels/irc">
    Classic IRC protocol
  </Card>
</CardGroup>
