Core Messaging Channels
These are the most commonly used channel adapters, covering the major consumer messaging platforms and built-in communication options.
Telegram
Prerequisites
- A Telegram bot token (from @BotFather)
Setup
- Open Telegram and message
@BotFather. - Send
/newbotand follow the prompts to create a new bot. - Copy the bot token.
- Set the environment variable:
export TELEGRAM_BOT_TOKEN=your-token # Environment variable
librefang vault set TELEGRAM_BOT_TOKEN # Encrypted vault (recommended)
librefang config set-key telegram # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
# Optional: restrict to specific Telegram user IDs
# allowed_users = ["123456789"]
[channels.telegram.overrides]
# Optional: Telegram-native HTML formatting
# output_format = "telegram_html"
# group_policy = "mention_only"
- Restart the daemon:
librefang start
How It Works
The Telegram adapter uses long-polling via the getUpdates API. It polls every few seconds with a 30-second long-poll timeout. On API failures, it applies exponential backoff (starting at 1 second, up to 60 seconds). Shutdown is coordinated via a watch::channel.
Messages from authorized users are converted to ChannelMessage events and routed to the configured agent. Responses are sent back via the sendMessage API. Long responses are automatically split into multiple messages to respect Telegram's 4096-character limit using the shared split_message() utility.
File Attachments
Files attached to Telegram messages arrive as temporary authenticated URLs that the LLM cannot access directly. The bridge downloads them to a configurable directory and rewrites the message into local-path content blocks — non-image files become a ContentBlock::Text with the saved path (the agent then calls file_read); images become ContentBlock::ImageFile. See Channel File Downloads on the overview for file_download_dir / file_download_max_bytes and the stale-file sweeper.
Interactive Setup
librefang channel setup telegram
This walks you through the setup interactively.
Discord
Prerequisites
- A Discord application and bot (from the Discord Developer Portal)
Setup
- Go to Discord Developer Portal.
- Click "New Application" and name it.
- Go to the Bot section and click "Add Bot".
- Copy the bot token.
- Under Privileged Gateway Intents, enable:
- Message Content Intent (required to read message content)
- Go to OAuth2 > URL Generator:
- Select scopes:
bot - Select permissions:
Send Messages,Read Message History - Copy the generated URL and open it to invite the bot to your server.
- Select scopes:
- Set the environment variable:
export DISCORD_BOT_TOKEN=your-token # Environment variable
librefang vault set DISCORD_BOT_TOKEN # Encrypted vault (recommended)
librefang config set-key discord # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"
- Restart the daemon.
How It Works
The Discord adapter connects to the Discord Gateway via WebSocket (v10). It listens for MESSAGE_CREATE events and routes messages to the configured agent. Responses are sent via the REST API's channels/{id}/messages endpoint.
The adapter handles Gateway reconnection, heartbeating, and session resumption automatically.
Slack
Prerequisites
- A Slack app with Socket Mode enabled
Setup
- Go to Slack API and click "Create New App" > "From Scratch".
- Enable Socket Mode (Settings > Socket Mode):
- Generate an App-Level Token with scope
connections:write. - Copy the token (
xapp-...).
- Generate an App-Level Token with scope
- Go to OAuth & Permissions and add Bot Token Scopes:
chat:writeapp_mentions:readim:historyim:readim:write
- Install the app to your workspace.
- Copy the Bot User OAuth Token (
xoxb-...). - Set the environment variables:
export SLACK_APP_TOKEN=xapp-... # Environment variable
export SLACK_BOT_TOKEN=xoxb-...
librefang vault set SLACK_APP_TOKEN # Encrypted vault (recommended)
librefang vault set SLACK_BOT_TOKEN
librefang config set-key slack # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
[channels.slack.overrides]
# Optional: Slack-native mrkdwn formatting
# output_format = "slack_mrkdwn"
# threading = true
- Restart the daemon.
How It Works
The Slack adapter uses Socket Mode, which establishes a WebSocket connection to Slack's servers. This avoids the need for a public webhook URL. The adapter receives events (app mentions, direct messages) and routes them to the configured agent. Responses are posted via the chat.postMessage Web API. When threading = true, replies are sent to the message's thread via thread_ts.
Processing-State Reactions
The Slack adapter shows live "I'm working on it" feedback by adding 👀 to the user's message on receive and replacing it with ✅ when the reply is sent. Toggle with the SLACK_REACTIONS environment variable (default true — set to false to disable). already_reacted and no_reaction errors are silently ignored, so reaction failures never block message processing. See Reactions and Processing State on the channels overview.
Prerequisites
- A Meta Business account with WhatsApp Cloud API access
Setup
- Go to Meta for Developers.
- Create a Business App.
- Add the WhatsApp product.
- Set up a test phone number (or use a production one).
- Copy:
- Phone Number ID
- Permanent Access Token
- Choose a Verify Token (any string you choose)
- Set environment variables:
export WA_PHONE_ID=your-phone-id # Environment variable
export WA_ACCESS_TOKEN=your-access-token
export WA_VERIFY_TOKEN=your-verify-token
librefang vault set WA_PHONE_ID # Encrypted vault (recommended)
librefang vault set WA_ACCESS_TOKEN
librefang vault set WA_VERIFY_TOKEN
librefang config set-key whatsapp # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.whatsapp]
mode = "cloud_api"
phone_number_id_env = "WA_PHONE_ID"
access_token_env = "WA_ACCESS_TOKEN"
verify_token_env = "WA_VERIFY_TOKEN"
default_agent = "assistant"
-
Set up a webhook in the Meta dashboard pointing to your server's public URL:
- URL:
https://your-domain.com:4545/channels/whatsapp/webhook - Verify Token: the value you chose above
- Subscribe to:
messages
- URL:
-
Restart the daemon.
How It Works
The WhatsApp adapter registers webhook routes on the shared API server (default port 4545) to receive incoming webhooks from the WhatsApp Cloud API. It handles webhook verification (GET) and message reception (POST). Responses are sent via the Cloud API's messages endpoint.
Group Chat Support
WhatsApp group messages are automatically detected. The bot's behavior in groups is controlled by the group_policy setting in channel overrides:
| Policy | Behavior |
|---|---|
mention_only (default) | Only respond when @mentioned |
all | Respond to all group messages |
commands_only | Only respond to slash commands |
ignore | Ignore all group messages |
mention_only matches the bot phone number or bot name in the message body — set bot_phone and bot_name (or pass via with_bot_phone() / with_bot_name() in code) so the matcher knows what to look for. trigger_only instead matches group_trigger_patterns (regex list).
Example:
[channels.whatsapp.overrides]
group_policy = "mention_only"
bot_phone = "+15551234567"
bot_name = "librefang"
# Or use trigger_only with regex patterns:
# group_policy = "trigger_only"
# group_trigger_patterns = ["(?i)\\bbot\\b", "(?i)\\bhelp\\b"]
Voice Messages
The WhatsApp adapter can send voice replies via send_voice(to, audio, mime_type). In Cloud API mode it uploads to /{phone_number_id}/media then sends an audio message with the returned media_id; in gateway mode it base64-encodes and POSTs to /message/send-voice. See WhatsApp Voice + DM/Group Policies on the channels overview.
WeChat (Personal)
Prerequisites
- A personal WeChat account (iOS 8.0.70+ recommended)
- WeChat ClawBot plugin access (currently in gradual rollout)
Setup
- Add to config:
[channels.wechat]
# bot_token is obtained automatically via QR scan
# If you have a token from a previous session, set it here to skip QR login:
# bot_token_env = "WECHAT_BOT_TOKEN"
default_agent = "assistant"
allowed_users = [] # empty = allow all; format: "hash@im.wechat"
- Start (or restart) the LibreFang daemon.
- A QR code will appear in the daemon logs — scan it with your WeChat app.
- Once confirmed, the adapter begins receiving messages.
Tip: After the first QR login, save the
bot_tokento an environment variable so subsequent restarts skip the QR flow.
How It Works
The WeChat adapter uses Tencent's official iLink protocol (ilinkai.weixin.qq.com), the same protocol behind the WeChat ClawBot plugin. No third-party proxies or unofficial APIs are involved.
Connection flow:
- QR Login — calls
GET /ilink/bot/get_bot_qrcodeto generate a QR code, then pollsGET /ilink/bot/get_qrcode_statusuntil the user scans and confirms. Returns abot_tokenfor all subsequent requests. - Long-Polling — calls
POST /ilink/bot/getupdateswith a cursor (get_updates_buf). The server holds the connection for up to 35 seconds until new messages arrive, then returns them along with an updated cursor. - Sending — calls
POST /ilink/bot/sendmessagewith thecontext_tokenfrom the inbound message to associate the reply with the correct conversation. - Typing — calls
POST /ilink/bot/sendtypingwith atyping_ticket(fetched fromPOST /ilink/bot/getconfig) to show a typing indicator.
Supported message types: text, image, voice, file, video (all 5 iLink item types).
Reconnection: If a bot_token is configured, the adapter skips QR login and resumes polling immediately. On network errors, exponential backoff is applied (2s → 60s max).
Limitations
- Media upload is not yet supported (CDN flow with AES-128-ECB encryption). Incoming media is received; outgoing media falls back to text placeholders.
- Group chat detection is not yet implemented.
- Streaming responses are not yet supported (messages are sent as complete text).
- Grayscale rollout — the iLink API may not be available to all WeChat accounts yet.
Signal
Prerequisites
- Signal CLI installed and linked to a phone number
Setup
- Install signal-cli.
- Register or link a phone number.
- Add to config:
[channels.signal]
signal_cli_path = "/usr/local/bin/signal-cli"
phone_number = "+1234567890"
default_agent = "assistant"
- Restart the daemon.
How It Works
The Signal adapter spawns signal-cli as a subprocess in daemon mode and communicates via JSON-RPC. Incoming messages are read from the signal-cli output stream and routed to the configured agent.
Plain-Text Output by Default
Signal defaults to OutputFormat::PlainText because signal-cli renders Markdown asterisks and underscores literally. To re-enable Markdown output, override the format on the agent or channel — see Signal Plain-Text Default on the channels overview.
Media Attachments
The Signal adapter delivers Image, Voice, Video, Audio, Animation, File, FileData, and MediaGroup content blocks. Media URLs are downloaded and base64-encoded into base64_attachments on /v2/send. Unsupported types (Poll, Sticker, …) degrade gracefully — text-only fallback or skip — rather than fail the whole reply. See Signal Media Attachments for the full type list.
Matrix
Prerequisites
- A Matrix homeserver account and access token
Setup
- Create a bot account on your Matrix homeserver.
- Generate an access token.
- Set the environment variable:
export MATRIX_TOKEN=your-token # Environment variable
librefang vault set MATRIX_TOKEN # Encrypted vault (recommended)
librefang config set-key matrix # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.matrix]
homeserver_url = "https://matrix.org"
access_token_env = "MATRIX_TOKEN"
user_id = "@librefang-bot:matrix.org"
default_agent = "assistant"
- Invite the bot to the rooms you want it to monitor.
- Restart the daemon.
How It Works
The Matrix adapter uses the Matrix Client-Server API. It syncs with the homeserver using long-polling (/sync with a timeout) and processes new messages from joined rooms. Responses are sent via the /rooms/{roomId}/send endpoint.
Prerequisites
- An email account with IMAP and SMTP access
Setup
- For Gmail, create an App Password.
- Set the environment variable:
export EMAIL_PASSWORD=your-password # Environment variable
librefang vault set EMAIL_PASSWORD # Encrypted vault (recommended)
librefang config set-key email # .env file
# Or set via dashboard "Set API Key" button # secrets.env
- Add to config:
[channels.email]
imap_host = "imap.gmail.com"
imap_port = 993
smtp_host = "smtp.gmail.com"
smtp_port = 587
username = "you@gmail.com"
password_env = "EMAIL_PASSWORD"
poll_interval = 30
default_agent = "email-assistant"
- Restart the daemon.
How It Works
The email adapter polls the IMAP inbox at the configured interval. New emails are parsed (subject + body) and routed to the configured agent. Responses are sent as reply emails via SMTP, preserving the subject line threading.
WebChat (Built-in)
The WebChat UI is embedded in the daemon and requires no configuration. When the daemon is running:
http://127.0.0.1:4545/
Features:
- Real-time chat via WebSocket
- Streaming responses (text deltas as they arrive)
- Agent selection (switch between running agents)
- Token usage display
- No authentication required on localhost (protected by CORS)
Message Truncation for Platform Limits
Source: librefang-channels/src/message_truncator.rs
Messaging platforms impose hard character limits on outbound messages, counted in UTF-16 code units rather than bytes or Unicode code points. LibreFang measures message length in UTF-16 units and handles oversized messages automatically.
Platform Limits
| Platform | UTF-16 unit limit | Behavior when exceeded |
|---|---|---|
| Telegram | 4 096 | Message is split into multiple sequential messages |
| Discord | 2 000 | Message is split into multiple sequential messages |
| Other channels | Configurable | Truncated by default unless split is enabled |
How UTF-16 Length Differs from Byte Length
Most ASCII text has the same byte count and UTF-16 unit count. The difference matters for:
- Emoji (
😀): 2 UTF-16 units, but 4 bytes in UTF-8 - Supplementary CJK characters: 2 UTF-16 units, 4 bytes in UTF-8
- Basic CJK / Korean / Arabic: 1 UTF-16 unit, 3 bytes in UTF-8
A string that fits in 2 000 bytes may still exceed 2 000 UTF-16 units, or vice versa. Using byte length as a proxy silently produces truncated or rejected messages.
Truncation
truncate_to_utf16_limit(text, limit) uses binary search over the string's UTF-16
unit positions to find the longest prefix that fits, then cuts at a valid Unicode scalar
boundary. The result is never split mid-codepoint or mid-surrogate-pair.
Splitting
split_to_utf16_chunks(text, limit) divides the message into sequential chunks, each
within limit UTF-16 units. LibreFang sends the chunks as separate messages in order,
preserving all content. The split boundary respects Unicode scalar boundaries — no
chunk ends in the middle of a character.
Disabling Automatic Splitting
If you want LibreFang to truncate rather than split long messages for a specific
channel, set split_long_messages = false in the channel configuration:
[channels.telegram]
split_long_messages = false # truncate at 4096 instead of splitting
The default is true for Telegram and Discord.