A messaging channel that runs through a third-party bot is still the vendor’s channel, however reliably it delivers. The bot my agents reported through did its job, which is why the dependency was easy to keep and worth breaking. I held neither the account, the app, nor the delivery path.
The channel sat on the self-managed server I had already consolidated four sites onto: the replacement had to live there without disturbing what already ran.
TL;DR
Replacing the bot was a posture, not a patch. I moved my Telegram gateway onto Matrix, the network I now run myself, and deployed Synapse as a non-federated, registration-closed homeserver with end-to-end encryption (E2EE) off, for sovereignty rather than to fix a failure.
The channel ran through someone else’s bot
The gateway was a third-party bot that worked. The problem was ownership, not behavior. Every message passed across a vendor’s servers, on an account I did not control. The channel was mine to use and not mine to keep.
The same box already ran an Apache web server in front of MySQL. Nothing in the stack could feel the change, and it had to fit inside roughly 0.5GB to 1GB of added RAM, against about 6GB free of 7.9GB.
The real question was not which bot
Framed as tooling, the answer is a better bot. That framing hides the decision. A gateway that behaves still leaves the sovereign channel in a vendor’s hands: the fix is a messaging server I run.
That is the same sovereignty test I apply when a third party decides the shape of my stack: whether I can keep a service working without its permission.
The decision: a private island
I chose a private island: a messaging surface that stays mine end to end.
- A Synapse homeserver on the box I already run, with its own local PostgreSQL database and the Apache in front on 443.
- Non-federated and registration-closed: no federation, no public rooms, registration shut.
- Encryption off, permanently, not an inherited default.
- PostgreSQL kept to Synapse. MySQL is untouched, and the WordPress sites it serves are unaffected.
- A permanent server name: my homeserver’s name becomes the domain of every account ID and cannot change after the first start.
- Separate automation accounts, a human admin plus two non-admin accounts, so each token rotates on its own.
Two live options lost. Keeping the Telegram gateway was rejected on sovereignty grounds, not because it failed. A room allowlist as the primary gate was rejected as insufficient: a two-member room is treated as a direct message and bypasses the room allowlist entirely.
What I built
The homeserver went up with a local, Synapse-only PostgreSQL instance beside it and Apache left in front on 443. The island is deliberately dark.
This was a team effort. I led the AI Engineering Team through the migration, with the official Hermes Agent documentation as the reference: Ren on the architecture, Takumi moving the automations onto Matrix support, and Tomoe on the technical documentation.
Three accounts hold the channel: one admin and two non-admin accounts, each with its own token, so rotating one leaves the others untouched.
The gateway enforces the hardest gate at the user level: only @jn can trigger the agent, the automation accounts are not on that list, and notices are not processed, so an automation post is ignored twice over.
Then the old channel was decommissioned: the Telegram send script was deleted, its library dependency dropped, and the bots removed from Telegram’s own servers. Rollback here is a rebuild, not a configuration flip.
What broke: nothing
Nothing broke. All three records behind this piece report it the same way, and I will not dress that into an incident it was not. The move was a deliberate sovereignty choice on a working gateway.
The honest friction is what the outcome rests on. Telegram’s exit is one-way by design, so getting the channel back would be a recovery exercise, not a switch to flip.
The outcome, and its status
A fully self-hosted, non-federated, registration-closed homeserver replaces Telegram. Matrix is the only live channel my agents use, Telegram is unrecoverable by configuration, and the user allowlist means even a wrong room allowlist cannot let anyone but @jn trigger the agent.
All three records are partial: each is narrated from a wiki page, not read off a running system. I claim the posture and its status, not a measured property: the island is designed and standing, and I assert no uptime or throughput I never took.
This channel is also the transport my backups use to raise an alert. That ties the island back to the resilience frame it belongs to.
The operator behind this stack, and its trade-offs, is on the about page. If this is the shape of your problem, let’s talk.
