Channels
A channel is a chat platform you can reach nanoinfra on. It is also the word the product uses: the setup surface is Settings → Channels. This page connects nanoinfra to Telegram, Discord, Slack, Signal, Email, Mattermost and other chat apps, and is the full channel reference. If you want a focused setup path for one platform, start with a guide:
Want to build your own channel? See the Channel Package Guide.
Before configuring a chat app, make sure the local CLI path works:
nanoinfra agent -m "Hello!"
If that fails, fix installation, config, provider, or model setup first with quick-start.md, providers.md, and troubleshooting.md. Chat apps require nanoinfra gateway to stay running after the channel is configured.
Recommended Setup in the WebUI
For normal local setup, let the WebUI write and validate the channel config:
- Run
nanoinfra webui. - Open Settings → Channels.
- Search for the platform and open its setup panel.
- Follow the credential fields or QR flow. The screen tells you which platform-side token, permission, account, or URL it needs.
- Let nanoinfra install the optional channel support when prompted.
- Restart from the WebUI if it reports that a restart is required.
- Send a private test message. If the channel returns a pairing code, approve the pending request in the WebUI and send the message again.
If your installed stable release does not show Settings → Channels, continue with the manual setup pattern below or install current source.
A WebUI on the same machine may install optional packages by default. Remote browser clients cannot change the Python environment unless an administrator explicitly enables that capability. Run nanoinfra plugins enable <channel> locally when the guided install is unavailable.
The sections below explain what each chat platform requires and provide manual config for deployments that manage config.json directly.
[!NOTE] If you upgrade from a version that installed chat app SDKs by default, enable the channel in the same Python environment. That makes nanoinfra install its manifest-declared dependencies:
nanoinfra plugins enable <channel>Replace
<channel>with names such astelegram,slack,discord,matrix,signal,mattermost, ormsteams. To turn a channel off later, runnanoinfra plugins disable <channel>. nanoinfra keeps the saved settings, but stops loading that channel after the next restart.
Manual Setup Pattern
Most examples below are snippets to merge into ~/.nanoinfra/config.json. When a snippet includes allowFrom, it is showing a static allowlist. For pairing-based access on supported channels, omit allowFrom. Slack and Mattermost also need dm.policy set to "allowlist" before DMs issue pairing codes.
Every chat app uses the same shape:
- Create or prepare the bot/account in the chat platform.
- Copy the token, secret, QR login state, webhook URL, or account ID that platform gives you.
- Merge that platform's JSON snippet into
~/.nanoinfra/config.json. - Prefer pairing for DM-capable channels: omit
allowFrom, let the first DM receive a pairing code, then approve it with/pairing approve <code>. - For channels without pairing, such as Email, keep access narrow with
allowFromor the platform-specific allow list. - Check that nanoinfra can see the configured channel:
nanoinfra channels status
- Start the gateway and leave that terminal running:
nanoinfra gateway
- Send a test DM. If the bot returns a pairing code, approve it and send the message again.
In group chats, follow that channel's group policy. Most channels read
groupPolicy, and Signal reads the nestedgroup.policyandgroup.requireMentioninstead. Many channels default to mention-only. Matrix and WhatsApp default to open group replies.
If nanoinfra channels status does not show the channel as enabled, one of these is true:
- The config snippet is in the wrong place.
- The channel name is misspelled.
- The config file you edited is not the one nanoinfra reads.
If the channel is enabled but messages do not arrive, run nanoinfra gateway --verbose. Then
compare the platform-side credentials, event permissions, and allow lists.
allowFrom: ["*"]bypasses pairing and allows anyone who can reach that channel to talk to the bot. Use it only when that is intentional, or temporarily while testing in a private sandbox.
Each platform has its own section below.
| Channel | What you need |
|---|---|
| Telegram | Bot token from @BotFather |
| Discord | Bot token + Message Content intent |
QR code scan (nanoinfra channels login whatsapp) | |
| Slack | Bot token + App-Level token |
| Mattermost | Bot account token + server URL |
| Matrix | Homeserver URL + Access token |
| IMAP/SMTP credentials | |
| Microsoft Teams | App ID + App Password + public HTTPS endpoint |
| Signal | signal-cli daemon + phone number |
Group Policy
groupPolicy sets when the bot replies in a group chat or channel:
"mention"— the bot replies only when a message @mentions it."open"— the bot replies to every message in the group."allowlist"— the bot replies only in the groups or channels listed ingroupAllowFrom.
Discord accepts "mention" and "open" only. Signal does not use groupPolicy at all. It uses
the nested group.policy and group.requireMention fields instead. Each platform section below
gives its own default.
Answering agent
Every channel here may name the agent that answers it. Add agent to that channel's block:
{
"channels": {
"telegram": { "token": "...", "agent": "sre" }
}
}
Then every message arriving on that channel is answered by sre. A sender can still address
another configured agent with @agent:<name>, which wins over the binding.
Set it in Settings → Channels instead, in the Answering agent section of the channel's
panel. The section is present only when the deployment names agents in agents.named.
Two rules, both applied when config loads:
- The name has to exist in
agents.named. A name that does not refuses to load, and the message names it. channels.websocket.agentis refused. The WebUI picks the agent for each message in the composer, so a channel-wide default there would answer every turn where you picked nothing.
See Agents for the order all four sources are read in.
Telegram
Recommended WebUI setup
- Create a bot with
@BotFatherand copy its token. - Run
nanoinfra webui, then open Settings → Channels → Telegram. - Paste the token. If the gateway cannot reach Telegram directly, expand Advanced and add an HTTP or SOCKS proxy.
- Save and enable Telegram, then send the bot a direct message.
The configuration badge means nanoinfra found a saved token. The live connection check is separate, so a temporary Telegram or proxy outage does not make an existing configuration disappear. Saved tokens and proxy URLs remain masked.
See the step-by-step Telegram guide for pairing and troubleshooting.
Manual setup
1. Create a bot
- Open Telegram, and search for
@BotFather. - Send
/newbot, and follow the prompts. - Copy the token.
2. Configure
{
"channels": {
"telegram": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"allowFrom": ["YOUR_USER_ID"]
}
}
}
If the gateway cannot reach Telegram directly, add a proxy to the same section:
{
"channels": {
"telegram": {
"proxy": "http://127.0.0.1:7890"
}
}
}
HTTP, HTTPS, SOCKS5, and SOCKS5H proxy URLs are accepted. Treat a proxy URL containing a username or password as a secret.
You can find your User ID in Telegram settings. It is shown as
@yourUserId. Copy this value without the@symbol and paste it into the config file.
richMessagesdefaults tofalse. Set it totrueonly if your Telegram client supports Bot API 10.1 rich messages and you want richer markdown rendering. Keep it disabled for Telegram Web, which may show unsupported-message errors for rich messages.
Webhook mode (optional)
Telegram uses long polling by default. To receive updates through a webhook, expose a public HTTPS URL that forwards to nanoinfra's local listener and set mode to webhook:
{
"channels": {
"telegram": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"mode": "webhook",
"webhookUrl": "https://example.com/telegram",
"webhookListenHost": "127.0.0.1",
"webhookListenPort": 8081,
"webhookPath": "/telegram",
"webhookSecretToken": "CHANGE_ME_RANDOM_SECRET",
"webhookMaxConnections": 4,
"allowFrom": ["YOUR_USER_ID"]
}
}
}
webhookSecretTokenis required in webhook mode. Do not expose the local webhook listener directly to the public internet without a reverse proxy or tunnel in front of it. TLS/Host policy is handled by your proxy. nanoinfra only listens onwebhookListenHost:webhookListenPortand validates Telegram's webhook secret token.webhookMaxConnectionsdefaults to4. nanoinfra still serializes Telegram updates per conversation before forwarding them to the agent.
webhookUrlis the public HTTPS URL registered with Telegram.webhookPathis the local path nanoinfra listens on. They often use the same path, but may differ when a reverse proxy or tunnel rewrites the request path.
Discord
1. Create a bot
- Go to https://discord.com/developers/applications.
- Create an application → Bot → Add Bot.
- Copy the bot token.
2. Enable intents
- In the Bot settings, enable MESSAGE CONTENT INTENT.
- (Optional) Enable SERVER MEMBERS INTENT if you plan to use allow lists based on member data.
3. Get your User ID
- Discord Settings → Advanced → enable Developer Mode.
- Right-click your avatar → Copy User ID.
4. Configure
{
"channels": {
"discord": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"allowFrom": ["YOUR_USER_ID"],
"allowChannels": [],
"groupPolicy": "mention",
"streaming": true
}
}
}
groupPolicydefaults to"mention". DMs always respond when the sender is inallowFrom. If you set the group policy to open, create new threads as private threads, and then @ the bot into it. Otherwise the thread itself, and the channel you spawned it in, each spawn a bot session.allowChannelsrestricts the bot to specific Discord channel IDs. Empty (default) means respond in every channel the bot can see. Example:["1234567890", "0987654321"]. The filter applies afterallowFrom, so both must pass. Discord threads under an allowed parent channel are also allowed. For Forum channels, allowing the parent Forum channel allows all threads/posts in that forum.streamingdefaults totrue. Disable it only if you explicitly want non-streaming replies.
5. Invite the bot
- OAuth2 → URL Generator.
- Scopes:
bot. - Bot Permissions:
Send Messages,Read Message History. - Open the generated invite URL, and add the bot to your server.
Matrix
Element is the reference client for Matrix.
1. Create or choose a Matrix account
- Create or reuse a Matrix account on your homeserver (for example
matrix.org). - Confirm you can log in with Element.
2. Get credentials
- You need:
userId(example:@nanoinfra:matrix.org)password
(Note: accessToken and deviceId are still supported for legacy reasons, but for reliable encryption, password login is recommended instead. If the password is provided, accessToken and deviceId will be ignored.)
3. Configure
{
"channels": {
"matrix": {
"enabled": true,
"homeserver": "https://matrix.org",
"userId": "@nanoinfra:matrix.org",
"password": "mypasswordhere",
"e2eeEnabled": true,
"sasVerification": true,
"allowFrom": ["@your_user:matrix.org"],
"groupPolicy": "open",
"groupAllowFrom": [],
"allowRoomMentions": false,
"maxMediaBytes": 20971520
}
}
}
Keep a persistent
matrix-store— encrypted session state is lost if these change across restarts.
| Option | Description |
|---|---|
allowFrom | User IDs allowed to interact. Empty denies all. |
groupPolicy | Defaults to open. |
groupAllowFrom | Room allowlist (used when policy is allowlist). |
allowRoomMentions | Accept @room mentions in mention mode. |
e2eeEnabled | E2EE support (default true). Set false for plaintext-only. |
sasVerification | Auto-complete SAS device verification requests from allowed users (default false). Useful for Element X, which does not expose manual trust for third-party devices. |
maxMediaBytes | Max attachment size (default 20MB). Set 0 to block all media. |
WhatsApp
1. Link device with QR
nanoinfra channels login whatsapp
# Scan QR with WhatsApp → Settings → Linked Devices
2. Configure
{
"channels": {
"whatsapp": {
"enabled": true,
"allowFrom": ["1234567890"]
}
}
}
For groups, allowFrom can contain either a participant sender ID/LID or a
group JID/bare group ID. A participant entry allows that sender wherever the bot
can see them. A group entry allows replies in that group.
Optional session database path:
{
"channels": {
"whatsapp": {
"databasePath": "~/.nanoinfra/whatsapp-auth/neonize.db"
}
}
}
Migrating from the old bridge
- Remove
bridgeUrlandbridgeToken. WhatsApp no longer runs a local Node.js bridge. - Re-run
nanoinfra channels login whatsapp. Old Baileys bridge auth data is not reused by neonize. - Update
allowFromentries to the WhatsApp sender ID without a leading+.
Optional: static LID mappings
Modern WhatsApp can deliver a sender's LID instead of their phone number. nanoinfra learns LID to phone mappings at runtime when both identifiers are present. You can also seed mappings up front, so the phone number resolves from the very first message:
{
"channels": {
"whatsapp": {
"enabled": true,
"allowFrom": ["1234567890"],
"lidMappings": { "123456789012345": "1234567890" }
}
}
}
Slack
Uses Socket Mode — no public URL required.
1. Create a Slack app
- Go to Slack API → Create New App → "From scratch".
- Pick a name, and select your workspace.
2. Configure the app
- Socket Mode: Toggle ON → Generate an App-Level Token with
connections:writescope → copy it (xapp-...). - OAuth & Permissions: Add bot scopes:
chat:write,reactions:write,app_mentions:read,files:read,files:write,channels:history,groups:history,im:history,mpim:history. - Event Subscriptions: Toggle ON → Subscribe to bot events:
message.im,message.channels,app_mention→ Save Changes. - App Home: Scroll to Show Tabs → Enable Messages Tab → Check "Allow users to send Slash commands and messages from the messages tab".
- Install App: Click Install to Workspace → Authorize → copy the Bot Token (
xoxb-...).
files:readis required to read files users send to nanoinfra.files:writeis required for nanoinfra to send images, videos, and other file uploads. If you add either scope later, reinstall the Slack app to the workspace and restart nanoinfra so it uses the updated bot token.
3. Configure nanoinfra
{
"channels": {
"slack": {
"enabled": true,
"botToken": "xoxb-...",
"appToken": "xapp-...",
"allowFrom": ["YOUR_SLACK_USER_ID"],
"groupPolicy": "mention"
}
}
}
DM the bot directly or @mention it in a channel — it should respond!
[!TIP]
groupPolicydefaults to"mention".groupAllowFrom: channel IDs the bot may respond in whengroupPolicyis"allowlist".groupRequireMention: whentrueandgroupPolicyis"allowlist", the bot only replies to channels ingroupAllowFromand only when @mentioned, instead of to every message. It has no effect whengroupPolicyis"mention"or"open". Use this to scope the bot to approved channels while keeping mention-only behavior.- DM policy defaults to open. Set
"dm": {"enabled": false}to disable DMs.
Mattermost
Mattermost needs no optional dependency: it ships in the base install.
Recommended WebUI setup
- Create a bot account in System Console → Integrations → Bot Accounts and copy its token.
- Run
nanoinfra webui, then open Settings → Channels → Mattermost. - Enter the server URL and the token.
serverUrlandtokenare the only required fields. - Save and enable Mattermost, then send the bot a direct message.
Manual setup
{
"channels": {
"mattermost": {
"enabled": true,
"serverUrl": "https://mattermost.example.com",
"token": "YOUR_MATTERMOST_TOKEN",
"teamId": "YOUR_TEAM_ID",
"groupPolicy": "mention",
"groupPolicyInThread": "open",
"replyInThread": true,
"dm": {
"policy": "allowlist"
}
}
}
}
teamId scopes the channel to one Mattermost team. groupPolicy defaults to "mention".
groupPolicyInThread governs replies inside a thread, and it inherits groupPolicy when
omitted. Set it to "open" when a follow-up in a thread should not need another @mention.
Mattermost issues pairing codes for direct messages only when dm.policy is "allowlist".
See the step-by-step Mattermost guide for the bot-account steps and troubleshooting.
Email
Give nanoinfra its own email account. It polls IMAP for incoming mail and replies via SMTP — like a personal email assistant.
1. Get credentials (Gmail example)
- Create a dedicated Gmail account for your bot (e.g.
my-nanoinfra@gmail.com). - Enable 2-Step Verification → Create an App Password.
- Use this app password for both IMAP and SMTP.
2. Configure
consentGrantedmust betrueto allow mailbox access. This is a safety gate — setfalseto fully disable.allowFrom: Add your email address.smtpUseTlsandsmtpUseSsldefault totrue/falserespectively, which is correct for Gmail (port 587 + STARTTLS). No need to set them explicitly.- Set
"autoReplyEnabled": falseif you only want to read/analyze emails without sending automatic replies.postAction: Optional post-processing for processed emails:"delete"or"move"(defaultnull). This runs only after an accepted email is successfully delivered to the AI pipeline.postActionMoveMailbox: Destination mailbox used whenpostActionis"move"(for example"Processed"or"[Gmail]/Trash").postActionIgnoreSkipped: Iftrue(default), skipped emails are ignored for post-action and not moved/deleted.postActionExpunge: Whentrue, the channel allows a full-mailboxEXPUNGEfallback if UID-scoped expunge is unavailable or fails (defaultfalse). Enable only on very old IMAP servers that lack modern UIDPLUS support. Note that this fallback will expunge all messages marked as deleted in the mailbox, including ones not handled by the agent. Leaving this off is safe for all modern IMAP servers.allowedAttachmentTypes: Save inbound attachments matching these MIME types —["*"]for all, e.g.["application/pdf", "image/*"](default[]= disabled).maxAttachmentSize: Max size per attachment in bytes (default2000000/ 2MB).maxAttachmentsPerEmail: Max attachments to save per email (default5).
{
"channels": {
"email": {
"enabled": true,
"consentGranted": true,
"imapHost": "imap.gmail.com",
"imapPort": 993,
"imapUsername": "my-nanoinfra@gmail.com",
"imapPassword": "your-app-password",
"smtpHost": "smtp.gmail.com",
"smtpPort": 587,
"smtpUsername": "my-nanoinfra@gmail.com",
"smtpPassword": "your-app-password",
"fromAddress": "my-nanoinfra@gmail.com",
"allowFrom": ["your-real-email@gmail.com"],
"postAction": "move",
"postActionMoveMailbox": "[Gmail]/Trash",
"postActionIgnoreSkipped": true,
"postActionExpunge": false,
"allowedAttachmentTypes": ["application/pdf", "image/*"]
}
}
}
Microsoft Teams
MVP — direct messages only.
Direct-message text in/out, tenant-aware OAuth, conversation reference persistence. Uses a public HTTPS webhook — no WebSocket. You need a tunnel or reverse proxy.
1. Create a Teams / Azure bot app registration
Create or reuse a Microsoft Teams / Azure bot app registration. Set the bot messaging endpoint to a public HTTPS URL ending in /api/messages.
2. Configure
{
"channels": {
"msteams": {
"enabled": true,
"appId": "YOUR_APP_ID",
"appPassword": "YOUR_APP_SECRET",
"tenantId": "YOUR_TENANT_ID",
"host": "0.0.0.0",
"port": 3978,
"path": "/api/messages",
"allowFrom": ["*"],
"replyInThread": true,
"mentionOnlyResponse": "Hi — what can I help with?",
"validateInboundAuth": true,
"refTtlDays": 30,
"pruneWebChatRefs": true,
"pruneNonPersonalRefs": true,
"refTouchIntervalS": 300
}
}
}
replyInThread: truereplies to the triggering Teams activity when a storedactivity_idis available.mentionOnlyResponsecontrols what nanoinfra receives when a user sends only a bot mention (<at>nanoinfra</at>). Set to""to ignore mention-only messages.validateInboundAuth: trueenables inbound Bot Framework bearer-token validation (signature, issuer, audience, lifetime,serviceUrl). This is the safe default for public deployments. Only set it tofalsefor local development or tightly controlled testing.refTtlDays(default30) controls how old stored conversation refs can be before they are pruned.pruneWebChatRefs(defaulttrue) drops refs withwebchat.botframework.comservice URLs.pruneNonPersonalRefs(defaulttrue) drops refs whoseconversation_typeis notpersonal.refTouchIntervalS(default300) throttles how often successful sends refreshupdated_atfor active refs.
Signal
Uses signal-cli daemon in HTTP mode — receive messages via SSE, send via JSON-RPC.
1. Install signal-cli
Install signal-cli and register a phone number:
signal-cli -u +1234567890 register
signal-cli -u +1234567890 verify <CODE>
Start the daemon:
signal-cli -a +1234567890 daemon --http localhost:8080
2. Configure
{
"channels": {
"signal": {
"enabled": true,
"phoneNumber": "+1234567890",
"daemonHost": "localhost",
"daemonPort": 8080,
"dm": {
"enabled": true,
"policy": "open"
},
"group": {
"enabled": true,
"policy": "open",
"requireMention": true
}
}
}
}
phoneNumber: Your registered Signal phone number.daemonHost/daemonPort: Where signal-cli daemon is listening (defaultlocalhost:8080).dm.policy:"open"(anyone can DM) or"allowlist"(only listed numbers/UUIDs). When"allowlist", unlisted DM senders receive a pairing code.dm.allowFrom: List of allowed phone numbers or UUIDs (used when policy is"allowlist").group.policy:"open"(all groups) or"allowlist"(only listed group IDs).group.requireMention: Whentrue(default), the bot only responds in groups when @mentioned.group.allowFrom: List of allowed group IDs (used when group policy is"allowlist").attachmentsDir: Override the directory where signal-cli stores inbound attachments. Defaults to~/.local/share/signal-cli/attachments(the Linux default). Set this if signal-cli runs with a customXDG_DATA_HOMEor on macOS.groupMessageBufferSize: Number of recent group messages kept for context (default20, must be > 0).
[!TIP] The channel automatically reconnects to the signal-cli daemon with exponential backoff if the connection drops. Markdown in bot replies is automatically converted to Signal text styles (bold, italic, code, etc.).