Skip to main content

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 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:
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

start
function
Start the channel (connect, begin listening for messages)
stop
function
Stop the channel (disconnect, clean up resources)
send
function
Send a message to a target (user, channel, room, etc.)
name
function
Return the channel name (e.g., “telegram”, “discord”)
healthCheck
function
Health check — return true if the channel is operational
sendEvent
function
default:"optional"
Optional staged outbound event delivery (chunk/final). If null, runtime falls back to send() for .final and ignores .chunk
startTyping
function
default:"no-op"
Start processing indicator for a recipient (e.g., typing status)
stopTyping
function
default:"no-op"
Stop processing indicator for a recipient

Channel Messages

Channels emit and receive ChannelMessage structs:
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:
{
  "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

dm
enum
default:"allow"
Direct message permission policy:
  • allow — Allow all DMs
  • deny — Deny all DMs
  • allowlist — Only allow DMs from senders in the allowlist

Group Policy

group
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

Allowlist

allowlist
string[]
default:"[]"
List of allowed sender identifiers. Supports "*" wildcard for allow-all. Case-insensitive matching.

Message Splitting

Channels automatically split long messages at platform limits while respecting UTF-8 character boundaries:
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

Telegram

Long-polling bot with media support

Discord

WebSocket gateway integration

Slack

Socket mode and HTTP events

Signal

Private messaging via signal-cli

Nostr

Decentralized protocol support

IRC

Classic IRC protocol