Mid-March 2026 I created an empty repository and called it gchat. No team, no investors, no “let us help you package this up.”
Four hours later I had my first commit — initial protocol sketch.md, half a page of X3DH pseudocode, two links to Signal specifications, and an eighty-item TODO list.
I’m building an end-to-end encrypted messenger. Alone. And it’s a deliberate choice.
Why not pick Signal or Matrix and live peacefully
The first question any sane person asks. My answer is banal but honest: I wanted to see the whole stack with my own hands. The crypto protocol — not as “call a library”, but as a key matrix that comes to life in a debugger. Server logic — not as “some backend”, but as a concrete delivery model: queues, idempotency, prekey preludes, garbage collection for undelivered messages. The client — not as “well, Flutter”, but as a state machine that correctly handles flaky networks, key rotations, and background notifications.
You can assemble this from ready-made bricks. But then I won’t know where my actual seams are. And in a crypto product, seams turn into CVEs with numbers six months later.
Second reason, practical. I wanted a product where I make the decisions. No reports, no committees, no “let’s revisit this next sprint.” Working solo doesn’t mean you move faster than a team — you move differently. In a straight line.
What I built in the first two weeks
It was important to push the riskiest pieces into production right away, not spend another month writing “infrastructure.” So I went in reverse order from the usual MVP.
- Days 1–3. Protocol on paper: X3DH for handshake, Double Ratchet for forward secrecy, AES-GCM-256 for payload, Ed25519 for signatures, Argon2id for key derivation from a password. Drew every transition, every key, every invariant.
- Days 4–5. Black-box PyNaCl +
cryptography: built a prototype message exchange between two scripts in a REPL. No database yet, no network — just the maths. - Days 6–9. FastAPI + SQLAlchemy 2.0 async + Postgres: key bundles, prekey issuance, a basic relay. Zero business features. Goal — verify my keys survive in production columns.
- Days 10–14. Flutter prototype: chat screen, one-to-one, no groups, no attachments, no calls. But with real E2E against the production schema.
By the end of week two I had a message sent from one device to another, encrypted by the client, decrypted by the client, and a server that knew nothing about the contents. That’s the core.
What turned out harder than it looked
Three things.
Key rotation under offline conditions. When one device is silent for three days, Double Ratchet still has to catch up. This forced a rethink of how I store prekey bundles on the server and what counts as “expired.”
Delivery idempotency. The client retries. The backend has already dropped the message to avoid duplicates. The client retries again. In the end the UI shows “not delivered” for a message the recipient already read. Fix — deterministic message_id on the client plus a dedup index on the server with TTL.
CallKit on iOS. Not about crypto — about the operating system. About Apple considering it normal to deliver a VoIP push within 200 ms while your delivery from behind a VPN sometimes takes 800. What do you do? Prelude push via APNs + fallback through WebRTC relay-ICE.
What I took away from this
Solo mode isn’t heroism. It’s a different economy of attention. You can’t afford to write things you won’t maintain. Every abstraction, every layer, every file is a promise to your six-months-from-now self.
So GChat is written simply. No plugins, no “universal cores”, no rewrites to “an even more correct architecture.” One language for backend (Python), one for client (Dart), one protocol, one delivery model. And all of it runs on a single self-managed server that I update manually once a month.
If you have a question about how GChat is built — ping me on Telegram. If you need an engineer who can sketch a crypto protocol on a napkin, assemble the backend, and wire up APNs with FCM — you’re in the right place.
In the next notes: how I chose between Matrix compatibility and a custom format, why I dropped MongoDB, and why “the GChat landing page is harder than the messenger itself” is not a compliment.