Skip to content

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.

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 @BotFather
allowed_users = ["*"] # tighten after testing
stream_mode = "off" # off | partial
draft_update_interval_ms = 1000 # edit throttle for partial mode
mention_only = false # only respond to @mentions in groups
interrupt_on_new_message = false # cancel in-flight reply on a new message
FieldType / valuesDefaultMeaning
bot_tokenstring (required)Bot API token from @BotFather.
allowed_userslist[] (deny all)Telegram usernames (without @) or numeric user IDs, or "*".
stream_mode"off" | "partial""off"Reply delivery mode (see below).
draft_update_interval_msinteger1000Edit throttle while streaming a draft in partial mode.
mention_onlyboolfalseRequire an @mention before responding in group chats.
interrupt_on_new_messageboolfalseCancel 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:

Terminal window
revka daemon
# or, channels only:
revka channel start

You can also add the channel from the CLI instead of editing TOML by hand:

Terminal window
revka channel add telegram '{"bot_token":"123456:TELEGRAM-TOKEN","name":"my-bot"}'

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

Terminal window
revka channel bind-telegram 123456789
revka channel bind-telegram alice

Alternatively, if pairing is active the user can self-bind in-chat with a one-time code the operator supplies:

/bind 123456

On 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.).

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 via editMessageText as tokens arrive, then finalizes with the complete text. draft_update_interval_ms (default 1000) 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):

CommandEffect
/modelsShow available providers and current selection.
/models <provider>Switch provider for this sender’s session.
/modelShow the current model.
/model <model-id>Switch model for this sender’s session.
/newClear this sender’s conversation history and start fresh.

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.

Default Revka builds are lean and do not include Matrix. Compile with the channel-matrix Cargo feature:

Terminal window
cargo build --release --features channel-matrix
# combine with others as needed:
cargo build --release --features hardware,channel-matrix

If [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.)

Use a bot account on your homeserver and its access token. Add to ~/.revka/config.toml:

[channels_config.matrix]
homeserver = "https://matrix.example.com" # required
access_token = "syt_..." # required
user_id = "@revka:matrix.example.com" # recommended for E2EE
device_id = "DEVICEID123" # recommended for E2EE
room_id = "!room:matrix.example.com" # or alias: #ops:matrix.example.com
allowed_users = ["*"] # tighten after testing
stream_mode = "partial" # off | partial | multi_message
draft_update_interval_ms = 1500 # higher than Telegram for E2EE overhead
multi_message_delay_ms = 800
recovery_key = "" # optional: cross-signing recovery
FieldType / valuesDefaultMeaning
homeserverURL (required)Homeserver base URL.
access_tokenstring (required)Token for the bot account.
user_idstringBot user ID; recommended for E2EE session restore.
device_idstringStable device identity; recommended for E2EE (see below).
room_idstringCanonical room ID (!room:server) or alias (#name:server), resolved at runtime.
allowed_roomslist[]Additional room IDs to accept messages from.
allowed_userslist[] (deny all)Matrix user IDs, or "*".
stream_mode"off" | "partial" | "multi_message""off"*Reply delivery mode.
draft_update_interval_msinteger1500Edit throttle in partial mode (higher than Telegram for re-encryption + federation latency).
multi_message_delay_msinteger800Delay between paragraph sends in multi_message mode.
recovery_keystring""Cross-signing/key-backup recovery key (see below).
* The onboarding wizard writes 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.

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.

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:

  1. 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"}
  2. From a password login — if whoami omits device_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_token and device_id in your config.

  3. 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.

For E2EE diagnostics, raise the log level for the Matrix channel:

Terminal window
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 daemon

This surfaces session-restore confirmation, per-sync completion, OTK conflict state, health-check results, and transient-vs-fatal sync error classification.

If Matrix appears connected — checks pass, no errors — but the bot does not respond, validate these in order:

  1. Sender allowlist. Confirm the sender is allowed by allowed_users. An empty list denies everyone; set ["*"] temporarily to diagnose.

  2. 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.

  3. 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_id must match the bot. If device_id is missing, set device_id manually.

  4. 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 found warning means key-backup recovery is not yet enabled (usually non-fatal, but set up a recovery_key). If recipients see messages as “unverified,” verify/sign the bot device from a trusted session and keep device_id stable.

  5. Fresh restart. Restart the daemon after any config change, then send a new message (not old timeline history):

    Terminal window
    revka onboard --channels-only
    revka daemon