← Back to list

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…

Piyush Agarwal in The Startup · 2026-05-30 01:13 · 1 claps · 7.5 min read
#ai-agent #signal #matrix #privacy #e2ee
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General 🔒 · Cybersecurity

Source: Image by author

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-cli exists, 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)

Figure 1: High-level Architecture (Image by author)

Three things on one server (VPS or home lab):

  1. Traefik — reverse proxy on port 443, TLS via Let’s Encrypt
  2. Tuwunel — lightweight Rust Matrix homeserver (~80MB RAM)
  3. 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_REGISTRATION to 'false'.
  • RocksDB. Embedded database, no separate container. The volume at /var/lib/tuwunel is 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


메타데이터
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