Set up Telegram & Matrix
Configure the two most common chat channels, including Matrix E2EE and the #499-class no-reply FAQ.
Telegram and Matrix are the two channels most operators reach for first: Telegram for a zero-infrastructure bot you can talk to in minutes, and Matrix for self-hosted, end-to-end encrypted (E2EE) rooms. This guide walks both setups end to end — token and config, the /bind pairing flow, response streaming and reactions for Telegram, and the device-identity, recovery-key, and feature-flag details that make Matrix E2EE actually deliver replies. It closes with the #499-class “configured but no reply” FAQ.
Both channels are configured under [channels_config] in ~/.revka/config.toml and run inside revka daemon. If you have not connected a channel before, read Connect a messaging channel first for the allowlist model and the polling-vs-webhook distinction. Neither Telegram nor Matrix needs a public inbound port — both pull messages over an outbound connection, so they work behind NAT and on a Raspberry Pi.
Telegram
Section titled “Telegram”1. Create a bot and configure
Section titled “1. Create a bot and configure”Talk to @BotFather in Telegram, run /newbot, and copy the token it issues (it looks like 123456:ABC-DEF...). Add the channel to ~/.revka/config.toml:
[channels_config.telegram]bot_token = "123456:TELEGRAM-TOKEN" # required, from @BotFatherallowed_users = ["*"] # tighten after testingstream_mode = "off" # off | partialdraft_update_interval_ms = 1000 # edit throttle for partial modemention_only = false # only respond to @mentions in groupsinterrupt_on_new_message = false # cancel in-flight reply on a new message| Field | Type / values | Default | Meaning |
|---|---|---|---|
bot_token | string (required) | — | Bot API token from @BotFather. |
allowed_users | list | [] (deny all) | Telegram usernames (without @) or numeric user IDs, or "*". |
stream_mode | "off" | "partial" | "off" | Reply delivery mode (see below). |
draft_update_interval_ms | integer | 1000 | Edit throttle while streaming a draft in partial mode. |
mention_only | bool | false | Require an @mention before responding in group chats. |
interrupt_on_new_message | bool | false | Cancel the in-flight generation when the same sender messages again in the same chat. |
Start the daemon (or just the channels) and send your bot a message:
revka daemon# or, channels only:revka channel startYou can also add the channel from the CLI instead of editing TOML by hand:
revka channel add telegram '{"bot_token":"123456:TELEGRAM-TOKEN","name":"my-bot"}'2. /bind pairing
Section titled “2. /bind pairing”Because the allowlist denies everyone by default, a new user who messages the bot is refused. Revka replies with the exact operator command to run:
🔐 This bot requires operator approval.
Copy this command to operator terminal:`revka channel bind-telegram 123456789`
After operator runs it, send your message again.The operator allowlists that identity from a terminal — by numeric ID or by username (without @):
revka channel bind-telegram 123456789revka channel bind-telegram aliceAlternatively, if pairing is active the user can self-bind in-chat with a one-time code the operator supplies:
/bind 123456On a valid code Revka adds the sender to the allowlist at runtime, persists it to config, and replies ✅ Telegram account bound successfully. Invalid codes are rejected, and repeated invalid attempts trigger a temporary lockout (⏳ Too many invalid attempts. Retry in Ns.).
3. Streaming and reactions
Section titled “3. Streaming and reactions”Telegram supports two reply behaviors plus emoji acknowledgement reactions:
stream_mode = "off"(default) — the full reply is sent once generation completes.stream_mode = "partial"— Revka posts an editable draft and live-edits it viaeditMessageTextas tokens arrive, then finalizes with the complete text.draft_update_interval_ms(default1000) throttles how often the draft is edited, keeping you under Telegram rate limits.
When the bot starts working on a message it adds a randomized acknowledgement reaction drawn from ⚡️, 👌, 👀, 🔥, 👍, so the sender knows the message was received.
These in-chat runtime commands are also available to allowed Telegram senders (sender-scoped, no daemon restart):
| Command | Effect |
|---|---|
/models | Show available providers and current selection. |
/models <provider> | Switch provider for this sender’s session. |
/model | Show the current model. |
/model <model-id> | Switch model for this sender’s session. |
/new | Clear this sender’s conversation history and start fresh. |
Matrix
Section titled “Matrix”Matrix runs Revka in your own homeserver’s rooms, including E2EE rooms, via the Client-Server sync API. It is feature-gated — see the build flag below — and benefits from a stable device identity for encryption.
Build flag: channel-matrix
Section titled “Build flag: channel-matrix”Default Revka builds are lean and do not include Matrix. Compile with the channel-matrix Cargo feature:
cargo build --release --features channel-matrix# combine with others as needed:cargo build --release --features hardware,channel-matrixIf [channels_config.matrix] is present but the feature was not compiled in, revka channel list, revka channel doctor, and revka channel start report that the channel was intentionally skipped for this build. (On Windows, setup.bat --standard includes channel-matrix.)
Configure the Matrix channel
Section titled “Configure the Matrix channel”Use a bot account on your homeserver and its access token. Add to ~/.revka/config.toml:
[channels_config.matrix]homeserver = "https://matrix.example.com" # requiredaccess_token = "syt_..." # requireduser_id = "@revka:matrix.example.com" # recommended for E2EEdevice_id = "DEVICEID123" # recommended for E2EEroom_id = "!room:matrix.example.com" # or alias: #ops:matrix.example.comallowed_users = ["*"] # tighten after testingstream_mode = "partial" # off | partial | multi_messagedraft_update_interval_ms = 1500 # higher than Telegram for E2EE overheadmulti_message_delay_ms = 800recovery_key = "" # optional: cross-signing recovery| Field | Type / values | Default | Meaning |
|---|---|---|---|
homeserver | URL (required) | — | Homeserver base URL. |
access_token | string (required) | — | Token for the bot account. |
user_id | string | — | Bot user ID; recommended for E2EE session restore. |
device_id | string | — | Stable device identity; recommended for E2EE (see below). |
room_id | string | — | Canonical room ID (!room:server) or alias (#name:server), resolved at runtime. |
allowed_rooms | list | [] | Additional room IDs to accept messages from. |
allowed_users | list | [] (deny all) | Matrix user IDs, or "*". |
stream_mode | "off" | "partial" | "multi_message" | "off"* | Reply delivery mode. |
draft_update_interval_ms | integer | 1500 | Edit throttle in partial mode (higher than Telegram for re-encryption + federation latency). |
multi_message_delay_ms | integer | 800 | Delay between paragraph sends in multi_message mode. |
recovery_key | string | "" | Cross-signing/key-backup recovery key (see below). |
partial for new Matrix channels; existing configs without stream_mode default to off.
Streaming behaves like the other channels: partial posts an editable draft and updates it via Matrix m.replace edits; multi_message delivers paragraphs (split on \n\n) as separate messages and never splits a code fence. Both modes work in encrypted and unencrypted rooms — the matrix-sdk handles E2EE transparently.
Security: Matrix E2EE Channel
Section titled “Security: Matrix E2EE Channel”E2EE is handled by the matrix-sdk, but it only works if the bot has a stable device identity and can obtain the room’s encryption keys. The single most common failure — the bot appears connected but never replies in an encrypted room — almost always traces back to device identity or key sharing.
device_id and cross-signing recovery_key
Section titled “device_id and cross-signing recovery_key”Revka tries to read its identity from GET /_matrix/client/v3/account/whoami. If that does not return a device_id, set one manually so a new device is not registered on every restart (which breaks key sharing and verification).
Find your device_id one of three ways:
-
From
whoami— the easiest, if the token is bound to a device session:Terminal window curl -sS -H "Authorization: Bearer $MATRIX_TOKEN" \"https://matrix.example.com/_matrix/client/v3/account/whoami"{"user_id": "@bot:example.com", "device_id": "ABCDEF1234"} -
From a password login — if
whoamiomitsdevice_id(token created via admin API):Terminal window curl -sS -X POST "https://matrix.example.com/_matrix/client/v3/login" \-H "Content-Type: application/json" \-d '{"type":"m.login.password","user":"@bot:example.com","password":"...","initial_device_display_name":"Revka"}'Use both the returned
access_tokenanddevice_idin your config. -
From Element — log in as the bot, go to Settings → Sessions, and copy the Device ID of the active session.
Set both in config and keep device_id stable across restarts:
[channels_config.matrix]user_id = "@bot:example.com"device_id = "ABCDEF1234"A recovery key lets Revka automatically restore room keys and cross-signing secrets from server-side backup — so device resets, crypto-store deletions, and fresh installs recover without emoji verification or manual key sharing. Get it from Element under Settings → Security & Privacy → Encryption → Secure Backup (generate a Security Key if backup is not set up; it looks like EsTj 3yST y93F SLpB ...). Add it during onboarding (the wizard prompts E2EE recovery key (or Enter to skip):) or edit config directly:
[channels_config.matrix]recovery_key = "EsTj 3yST y93F SLpB jJsz ..."If secrets.encrypt = true (the default), the value is encrypted on the next config save. On startup with a valid key you should see:
Matrix E2EE recovery successful — room keys and cross-signing secrets restored from server backup.Debug logging
Section titled “Debug logging”For E2EE diagnostics, raise the log level for the Matrix channel:
RUST_LOG=revka::channels::matrix=debug revka daemon# even more detail from the SDK crypto layer:RUST_LOG=revka::channels::matrix=debug,matrix_sdk_crypto=debug revka daemonThis surfaces session-restore confirmation, per-sync completion, OTK conflict state, health-check results, and transient-vs-fatal sync error classification.
E2EE FAQ (#499-class “no reply”)
Section titled “E2EE FAQ (#499-class “no reply”)”If Matrix appears connected — checks pass, no errors — but the bot does not respond, validate these in order:
-
Sender allowlist. Confirm the sender is allowed by
allowed_users. An empty list denies everyone; set["*"]temporarily to diagnose. -
Room membership. The bot account must have joined the exact target room. If you used an alias (
#...), confirm it resolves to the room you expect. -
Token identity. Verify the token belongs to the bot account:
Terminal window curl -sS -H "Authorization: Bearer $MATRIX_TOKEN" \"https://matrix.example.com/_matrix/client/v3/account/whoami"The returned
user_idmust match the bot. Ifdevice_idis missing, setdevice_idmanually. -
E2EE key sharing. In an encrypted room the bot device must receive room keys from trusted devices; without them, encrypted events cannot be decrypted. A
matrix_sdk_crypto::backups: ... no backup key was foundwarning means key-backup recovery is not yet enabled (usually non-fatal, but set up arecovery_key). If recipients see messages as “unverified,” verify/sign the bot device from a trusted session and keepdevice_idstable. -
Fresh restart. Restart the daemon after any config change, then send a new message (not old timeline history):
Terminal window revka onboard --channels-onlyrevka daemon