Why I Moved My AI Assistant From Signal to Matrix
I started on Signal because end-to-end encryption (E2EE) is non-negotiable. It worked — until it didn’t. Then I ended up self-hosting…

Source: Image by author
Why I Moved My AI Assistant From Signal to Matrix
I started on Signal because end-to-end encryption (E2EE) is non-negotiable. It worked — until it didn’t. Then I ended up self-hosting Matrix.
Your AI assistant needs a messaging platform. It’s not a human contact. It’s software that lives in your chat app, reads your calendar, and knows your schedule. Most messaging platforms weren’t built for this — and it shows the moment you try.
I started on Signal — the obvious choice for anyone who cares about privacy. For a few weeks it held up. Then the linked-device model started dropping messages, Note to Self became a single cramped thread with no notification support, and I realized I was fighting the platform instead of building my assistant. Last week I moved to a self-hosted Matrix server. Here’s why every alternative failed, and how to build it yourself.
The Problem With Other Platforms
Signal: Great Messenger, Terrible Bot Platform
I started on Signal because it was the only messenger I trusted with the kind of data my assistant would see. It seemed like a natural fit. Within a few weeks, the friction turned into reliability problems — dropped messages, no way to separate workflows into different rooms, and a bot running through signal-cli that broke whenever Signal changed something server-side.
The platform is designed exclusively for humans, and the friction compounds fast:
- Phone number requirement. Your bot needs a real, verifiable phone number. SMS verification. Account recovery. SIM card management. For software.
- Note to Self is the only DM channel. No push notifications for your own messages. No multiple rooms for different contexts. One flat thread. Your assistant can’t run isolated sessions for separate workflows.
- No bot API.
signal-cliexists, but it's reverse-engineered and breaks without warning on server-side changes. Signal has shown no interest in publishing a bot API, yet. - Linked device fragility. The bot runs as a linked device, not primary.
Signal is the best encrypted messenger for humans. It’s a dead end for automated agents.
Telegram: The Temptation
Telegram’s Bot API is genuinely good. Register with @BotFather, get a token, done. Clean HTTP API, webhooks, inline keyboards, rich messages. Massive ecosystem.
One dealbreaker: no end-to-end encryption for bots.
Every message transits Telegram’s servers in plaintext. For an assistant that reads your email, calendar, and notes, that’s giving a company a raw, unencrypted feed of everything your assistant knows about you.
Telegram bots work for public-facing tools and notifications. They’re a privacy disaster for a personal assistant. If E2EE is non-negotiable — and for a bot that knows about your finances & emails, it should be — Telegram isn’t in the conversation. That leaves Signal and Matrix as the only real options.
Discord: Wrong Model
Discord’s bot API is mature. But the mental model is guild servers, not personal DMs with an assistant. Bots that read DMs require privileged intents and manual approval. The platform actively doesn’t want this use case.
Like Telegram, Discord is out once you require E2EE for a personal assistant workload. Signal and Matrix are the only platforms worth taking seriously.
Why Matrix Won
Matrix is a protocol first, a messenger second. That changes everything.
Bot support that isn’t an afterthought
Register a user, grab an access token, done. No phone number. No QR codes. Every Matrix Client treats bots as a core use case:
curl -X POST <https://matrix.yourdomain.com/_matrix/client/v3/register> \\
-H "Content-Type: application/json" \\
-d '{
"username": "my-bot",
"password": "a-strong-password",
"auth": {"type": "m.login.registration_token", "token": "your-registration-token"}
}'
Your bot gets a real identity — @my-bot:matrix.yourdomain.com — that behaves like any other user. It can be DMed, invited to rooms, and participate in encrypted conversations.
Self-hosting means your data actually stays yours
You don’t have to self-host. matrix.org is free. But when your assistant touches email, calendar, and personal files, the calculus shifts:
- Messages stay on your infrastructure
- No third party sees metadata — who talks to whom, when, how often
- Your assistant’s identity lives on your domain
E2EE that works for automated contexts
Matrix uses Olm/Megolm for encryption. Unlike Signal, the key exchange protocol was built with automated clients in mind. You handle key verification and device trust once, then it runs.
Multiple rooms, isolated sessions
You can create multiple DM rooms with the same bot (or even with multiple ones). Each room is an independent session:
- Main conversation room
- Dedicated rooms for specific workflows (finance, research, coding)
- Rooms that trigger on specific keywords
All in one app. All with notifications. Impossible on Signal’s single-thread Note to Self.
The Architecture

Figure 1: High-level Architecture (Image by author)
Three things on one server (VPS or home lab):
- Traefik — reverse proxy on port 443, TLS via Let’s Encrypt
- Tuwunel — lightweight Rust Matrix homeserver (~80MB RAM)
- RocksDB — embedded message store, no PostgreSQL needed
The assistant connects over HTTPS via the standard Matrix Client API. Federation is disabled — this is a private homeserver for you and your bot, not a public node.
Why Tuwunel?

The Build
Before You Deploy
You need a domain before touching Docker. Create an A record (or AAAA for IPv6) pointing matrix.yourdomain.com at your VPS IP. Open port 443 on the firewall — Traefik handles TLS via Let's Encrypt.
TUWUNEL_SERVER_NAME must match that hostname exactly. Your Matrix user IDs will be @you:matrix.yourdomain.com. Clients won't discover a non-federated server automatically — you'll enter the custom homeserver URL manually in Element.
With federation disabled, you don’t need .well-known files or port 8448. If you enable federation later, Tuwunel's docs add an nginx sidecar for .well-known discovery — Traefik can't serve those files itself.
Step 1: Deploy with Docker Compose
One file, two services. Traefik terminates TLS and routes to Tuwunel on the internal Docker network:
services:
traefik:
image: traefik:v3.3
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=you@yourdomain.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
networks:
- proxy
homeserver:
image: jevolk/tuwunel:latest
restart: unless-stopped
volumes:
- db:/var/lib/tuwunel
networks:
- proxy
environment:
TUWUNEL_SERVER_NAME: matrix.yourdomain.com
TUWUNEL_DATABASE_PATH: /var/lib/tuwunel
TUWUNEL_PORT: 6167
TUWUNEL_ADDRESS: 0.0.0.0
TUWUNEL_MAX_REQUEST_SIZE: 20000000
TUWUNEL_ALLOW_FEDERATION: 'false' # Private server
TUWUNEL_ALLOW_REGISTRATION: 'true' # Enable temporarily
TUWUNEL_REGISTRATION_TOKEN: 'generate-a-long-random-string'
labels:
- "traefik.enable=true"
- "traefik.http.routers.tuwunel.rule=Host(`matrix.yourdomain.com`)"
- "traefik.http.routers.tuwunel.entrypoints=websecure"
- "traefik.http.routers.tuwunel.tls.certresolver=letsencrypt"
- "traefik.http.services.tuwunel.loadbalancer.server.port=6167"
ulimits:
nofile:
soft: 1048567
hard: 1048567
volumes:
db:
letsencrypt:
networks:
proxy:
driver: bridge
Key decisions:
- No federation. Disabling federation removes
.well-known, port 8448, and key management. You don't need them for two users. - Registration token. This is the only way to create accounts. After registering yourself and your bot, flip
ALLOW_REGISTRATIONto'false'. - RocksDB. Embedded database, no separate container. The volume at
/var/lib/tuwunelis your entire data store. Back it up.
Tuwunel never touches the internet directly. Traefik terminates TLS and routes internally on port 6167.
docker compose up -d
curl <https://matrix.yourdomain.com/_tuwunel/server_version> # verify
Confirm the curl returns a version string before moving on — if TLS or DNS is misconfigured, everything downstream fails silently.
Step 2: Create Accounts and Lock Down
Register both accounts via the API with your registration token, then flip TUWUNEL_ALLOW_REGISTRATION to 'false' and restart. Two users. Zero open signups.
Client choice matters. When you log in on mobile, use Element X — not Element Classic. See “What I Learned #3” below.
Step 3: Wire Your Assistant
Four environment variables:
MATRIX_HOMESERVER=https://matrix.yourdomain.com
MATRIX_USER_ID=@my-bot:matrix.yourdomain.com
MATRIX_ACCESS_TOKEN=syt_...
MATRIX_ALLOWED_USERS=@you:matrix.yourdomain.com
Get the access token for the bot using the curl command directly:
curl -s -X POST <https://matrix.yourdomain.com/_matrix/client/v3/login> \\
-H "Content-Type: application/json" \\
-d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "my-bot"},
"password": "your-bot-password"
}'
What I Learned
1. E2EE silently drops bot messages
This is the number one “my bot stopped responding” cause, and it’s invisible. No error. No log. Just silence.
Element and Element X now enable encryption by default on new DMs. If the room is encrypted and your bot hasn’t completed key exchange, messages arrive as ciphertext and get discarded.
Check whether a room is encrypted:
curl -s "<https://matrix.yourdomain.com/_matrix/client/v3/rooms/!roomId:matrix.yourdomain.com/state>" \\
-H "Authorization: Bearer $TOKEN" | grep "m.room.encryption"
If you see m.megolm.v1.aes-sha2, that room is encrypted. Create a new unencrypted room instead of using the "Start DM" button to confirm if the bot is working or not.
2. Logging out kills the token
Your assistant’s access token is tied to a Matrix device session. Log into Element as the bot, then log out, and the token is revoked. Get the token via the login API instead — no Element session to accidentally kill.
3. Element X, not Element Classic
On iOS, use Element X. Element Classic is in maintenance mode — security fixes only since 2023. Element X is SwiftUI + Rust SDK with sliding sync. It requires iOS 18+. When logging in, tap “Change” on the homeserver field and enter your custom URL. A non-federated homeserver won’t show up in any directory.
Should You Do This?
YES: you’re running a personal assistant that touches sensitive data — email, calendar, notes, files. Self-hosting Matrix takes a few hours the first time — longer if DNS or TLS needs debugging, shorter on a second deploy — for a messaging backbone you control completely.
If you don’t want to (or feel overwhelmed) by maintaining the self-hosted infra for Tuwunel + Traefik — use [matrix.org](http://matrix.org). It's free, it works, and E2EE still protects message content. The only downside is that matrix.org can see metadata (who is sending a message to whom, analytics, etc.), but for most use cases that's an acceptable tradeoff. The actual messages are still end-to-end encrypted.
NO: your assistant only does public-facing notifications or you are comfortable with your data staying on Telegram’s servers in readable format. Telegram’s Bot API is excellent and you’ll be done in 10 minutes. The privacy calculus is different when the bot isn’t reading your email.
TL;DR
- Signal — great for humans, dead end for bots (linked devices, no API, Note to Self only).
- Telegram / Discord — excellent bot APIs, no E2EE for personal assistant workloads.
- Matrix (self-hosted) — the only stack that gives you bots + E2EE + data on your infra.
- Setup — a few hours first time: domain A record → Traefik + Tuwunel docker-compose → two accounts → wire your assistant.
- Don’t want to self-host? — use matrix.org. E2EE on messages, metadata tradeoff.
- Watch for — encrypted DMs silently drop bot messages; logging out of Element revokes tokens; use Element X on iOS.
A week in: separate rooms for different workflows, notifications that actually fire, no linked-device roulette. One-time setup cost, infrastructure I control.
References
- Tuwunel deployment docs — Docker, reverse proxy, domain requirements
- Tuwunel Docker guide — official compose files including Traefik
- Matrix Client-Server API — registration, login, and room APIs used in the build steps
- Element X — recommended mobile and desktop client
- Telegram Bot API — bots run without E2EE
- signal-cli — reverse-engineered Signal bridge used before the move to Matrix
메타데이터
- post_id
- 9bc4902f16c3
- slug
- why-i-moved-my-ai-assistant-from-signal-to-matrix-9bc4902f16c3
- url
- https://medium.com/@impiyush/why-i-moved-my-ai-assistant-from-signal-to-matrix-9bc4902f16c3
- canonical_url
- https://medium.com/@impiyush/why-i-moved-my-ai-assistant-from-signal-to-matrix-9bc4902f16c3
- author_url
- https://medium.com/@impiyush
- status
- ok
- fetched_at
- 2026-06-22 17:31:34