Channel Adapters
OpenFang connects to messaging platforms through 40 channel adapters, allowing users to interact with their agents across every major communication platform. Adapters span consumer messaging, enterprise collaboration, social media, community platforms, privacy-focused protocols, and generic webhooks.
All adapters share a common foundation: graceful shutdown via watch::channel, exponential backoff on connection failures, Zeroizing<String> for secrets, automatic message splitting for platform limits, per-channel model/prompt overrides, DM/group policy enforcement, per-user rate limiting, and output formatting (Markdown, TelegramHTML, SlackMrkdwn, PlainText).
All 40 Channels
Core (7)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Telegram |
Bot API long-polling |
TELEGRAM_BOT_TOKEN |
Telegram |
| Discord |
Gateway WebSocket v10 |
DISCORD_BOT_TOKEN |
Discord |
| Slack |
Socket Mode WebSocket |
SLACK_BOT_TOKEN, SLACK_APP_TOKEN |
Slack |
| WhatsApp |
Cloud API webhook |
WA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN |
WhatsApp |
| Signal |
signal-cli REST/JSON-RPC |
(system service) |
Signal |
| Matrix |
Client-Server API /sync |
MATRIX_TOKEN |
Matrix |
| Email |
IMAP + SMTP |
EMAIL_PASSWORD |
Email |
Enterprise (8)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Microsoft Teams |
Bot Framework v3 webhook + OAuth2 |
TEAMS_APP_ID, TEAMS_APP_SECRET |
Teams |
| Mattermost |
WebSocket + REST v4 |
MATTERMOST_TOKEN, MATTERMOST_URL |
Mattermost |
| Google Chat |
Service account webhook |
GOOGLE_CHAT_SA_KEY, GOOGLE_CHAT_SPACE |
Custom("google_chat") |
| Webex |
Bot SDK WebSocket |
WEBEX_BOT_TOKEN |
Custom("webex") |
| Feishu / Lark |
Open Platform webhook |
FEISHU_APP_ID, FEISHU_APP_SECRET |
Custom("feishu") |
| Rocket.Chat |
REST polling |
ROCKETCHAT_TOKEN, ROCKETCHAT_URL |
Custom("rocketchat") |
| Zulip |
Event queue long-polling |
ZULIP_EMAIL, ZULIP_API_KEY, ZULIP_URL |
Custom("zulip") |
| XMPP |
XMPP protocol (stub) |
XMPP_JID, XMPP_PASSWORD, XMPP_SERVER |
Custom("xmpp") |
Social (8)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| LINE |
Messaging API webhook |
LINE_CHANNEL_SECRET, LINE_CHANNEL_TOKEN |
Custom("line") |
| Viber |
Bot API webhook |
VIBER_AUTH_TOKEN |
Custom("viber") |
| Facebook Messenger |
Platform API webhook |
MESSENGER_PAGE_TOKEN, MESSENGER_VERIFY_TOKEN |
Custom("messenger") |
| Mastodon |
Streaming API WebSocket |
MASTODON_TOKEN, MASTODON_INSTANCE |
Custom("mastodon") |
| Bluesky |
AT Protocol WebSocket |
BLUESKY_HANDLE, BLUESKY_APP_PASSWORD |
Custom("bluesky") |
| Reddit |
OAuth2 polling |
REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, REDDIT_USERNAME, REDDIT_PASSWORD |
Custom("reddit") |
| LinkedIn |
Messaging API polling |
LINKEDIN_ACCESS_TOKEN |
Custom("linkedin") |
| Twitch |
IRC gateway |
TWITCH_TOKEN, TWITCH_CHANNEL |
Custom("twitch") |
Community (6)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| IRC |
Raw TCP PRIVMSG |
IRC_SERVER, IRC_NICK, IRC_PASSWORD |
Custom("irc") |
| Guilded |
WebSocket |
GUILDED_BOT_TOKEN |
Custom("guilded") |
| Revolt |
WebSocket |
REVOLT_BOT_TOKEN |
Custom("revolt") |
| Keybase |
Bot API polling |
KEYBASE_USERNAME, KEYBASE_PAPERKEY |
Custom("keybase") |
| Discourse |
REST polling |
DISCOURSE_API_KEY, DISCOURSE_URL |
Custom("discourse") |
| Gitter |
Streaming API |
GITTER_TOKEN |
Custom("gitter") |
Self-hosted (1)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Nextcloud Talk |
REST polling |
NEXTCLOUD_TOKEN, NEXTCLOUD_URL |
Custom("nextcloud") |
Privacy (3)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Threema |
Gateway API webhook |
THREEMA_ID, THREEMA_SECRET |
Custom("threema") |
| Nostr |
NIP-01 relay WebSocket |
NOSTR_PRIVATE_KEY, NOSTR_RELAY |
Custom("nostr") |
| Mumble |
TCP text protocol |
MUMBLE_SERVER, MUMBLE_USERNAME, MUMBLE_PASSWORD |
Custom("mumble") |
Workplace (4)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Pumble |
Webhook |
PUMBLE_WEBHOOK_URL, PUMBLE_TOKEN |
Custom("pumble") |
| Flock |
Webhook |
FLOCK_TOKEN |
Custom("flock") |
| Twist |
API v3 polling |
TWIST_TOKEN |
Custom("twist") |
| DingTalk |
Robot API webhook |
DINGTALK_TOKEN, DINGTALK_SECRET |
Custom("dingtalk") |
Notification (2)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| ntfy |
SSE pub/sub |
NTFY_TOPIC, NTFY_SERVER |
Custom("ntfy") |
| Gotify |
WebSocket |
GOTIFY_TOKEN, GOTIFY_URL |
Custom("gotify") |
Integration (1)
| Channel |
Protocol |
Env Vars |
ChannelType Variant |
| Webhook |
Generic HTTP with HMAC-SHA256 |
WEBHOOK_URL, WEBHOOK_SECRET |
Custom("webhook") |
Channel Configuration
All channel configurations live in ~/.openfang/config.toml under the [channels] section. Each channel is a subsection:
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
allowed_users = ["123456789"]
[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
# Enterprise example
[channels.teams]
app_id_env = "TEAMS_APP_ID"
app_secret_env = "TEAMS_APP_SECRET"
default_agent = "ops"
# Social example
[channels.mastodon]
token_env = "MASTODON_TOKEN"
instance = "https://mastodon.social"
default_agent = "social-media"
Common Fields
bot_token_env / token_env – The environment variable holding the bot/access token. OpenFang reads the token from this env var at startup. All secrets are stored as Zeroizing<String> and wiped from memory on drop.
default_agent – The agent name (or ID) that receives messages when no specific routing applies.
allowed_users – Optional list of platform user IDs allowed to interact. Empty means allow all.
overrides – Optional per-channel behavior overrides.
Environment Variables Reference (Core Channels)
| Channel |
Required Env Vars |
| Telegram |
TELEGRAM_BOT_TOKEN |
| Discord |
DISCORD_BOT_TOKEN |
| Slack |
SLACK_BOT_TOKEN, SLACK_APP_TOKEN |
| WhatsApp |
WA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN |
| Matrix |
MATRIX_TOKEN |
| Email |
EMAIL_PASSWORD |
Channel Overrides
Every channel adapter supports ChannelOverrides, which let you customize behavior per channel without modifying the agent manifest. Add an [channels.<name>.overrides] section in config.toml:
[channels.telegram.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Telegram assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "telegram_html"
usage_footer = "compact"
Override Fields
| Field |
Type |
Default |
Description |
model |
Option<String> |
Agent default |
Override the LLM model for this channel. |
system_prompt |
Option<String> |
Agent default |
Override the system prompt for this channel. |
dm_policy |
DmPolicy |
Respond |
How to handle direct messages. |
group_policy |
GroupPolicy |
MentionOnly |
How to handle group/channel messages. |
rate_limit_per_user |
u32 |
0 (unlimited) |
Max messages per minute per user. |
threading |
bool |
false |
Send replies as thread responses (platforms that support it). |
output_format |
Option<OutputFormat> |
Markdown |
Output format for this channel. |
usage_footer |
Option<UsageFooterMode> |
None |
Whether to append token usage to responses. |
Formatter, Rate Limiter, and Policies
Output Formatter
The formatter module (openfang-channels/src/formatter.rs) converts Markdown output from the LLM into platform-native formats:
| OutputFormat |
Target |
Notes |
Markdown |
Standard Markdown |
Default; passed through as-is. |
TelegramHtml |
Telegram HTML subset |
Converts **bold** to <b>, code to <code>, etc. |
SlackMrkdwn |
Slack mrkdwn |
Converts **bold** to *bold*, links to `<url |
PlainText |
Plain text |
Strips all formatting. |
Per-User Rate Limiter
The ChannelRateLimiter (openfang-channels/src/rate_limiter.rs) uses a DashMap to track per-user message counts.
DM Policy
Controls how the adapter handles direct messages:
| DmPolicy |
Behavior |
Respond |
Respond to all DMs (default). |
AllowedOnly |
Only respond to DMs from users in allowed_users. |
Ignore |
Silently drop all DMs. |
Group Policy
Controls how the adapter handles messages in group chats, channels, and rooms:
| GroupPolicy |
Behavior |
All |
Respond to every message in the group. |
MentionOnly |
Only respond when the bot is @mentioned (default). |
CommandsOnly |
Only respond to /command messages. |
Ignore |
Silently ignore all group messages. |
Telegram
Prerequisites
Setup
- Open Telegram and message
@BotFather.
- Send
/newbot and follow the prompts to create a new bot.
- Copy the bot token.
- Set the environment variable:
export TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
- Add to config:
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
- Restart the daemon:
openfang 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.
Discord
Prerequisites
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.
- Enable Message Content Intent required to read message content.
- Set the environment variable:
export DISCORD_BOT_TOKEN=MTIzNDU2Nzg5.ABCDEF.ghijklmnop
- 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) and routes messages to the configured agent.
Slack
Prerequisites
- A Slack app with Socket Mode enabled
Setup
- Go to Slack API and click "Create New App" > "From Scratch".
- Copy the token.
- Set the environment variables:
export SLACK_APP_TOKEN=xapp-1-...
export SLACK_BOT_TOKEN=xoxb-...
- Add to config:
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
- Restart the daemon.
How It Works
The Slack adapter uses Socket Mode to receive events and routes them to the configured agent.