Chat is the substrate. On top of it: a command palette, keybinds, macros, pipelines and client plugins. Encrypted DMs and groups with MLS. Your server, or a relay that never holds your keys.
RIM is not a Slack, Discord or Matrix replacement. It is for people who want what those do not offer: a chat workspace they can shape with commands, keybinds, macros and plugins, running on a box they control. The closest relatives are VS Code, Raycast and Obsidian, applied to team communication.
A Rust server and a Tauri desktop and web client. Chat with threads, attachments and search; a command registry with macros and pipelines; client plugins; key-based identity; MLS-encrypted DMs and groups; relays and hosts talking over a signed protocol.
A vanilla core that is a decentralized chat platform plus the programmable layer. Everything domain-specific lives in plugins, bots and pipelines, never in the server. Release readiness, a first-run path, hybrid post-quantum signatures and a mobile web app are next.
A platform that dictates how teams work, a race for integrations and user counts, or a product that competes on chat polish alone. It competes on being the most programmable, keyboard-efficient communication environment.
Fuzzy-searchable, typed, with sensitivity tiers. Plugins register into the same registry as the built-ins.
Bind anything to any key, scoped by context. Three built-in profiles; export yours to a file and import someone else's. An opt-in Vim layer for moving around.
Chain commands into one keystroke; react to events with pipelines. The workspace adapts to how you work.
Panels, commands and context actions in a sandbox with capability grants. A signed workspace registry for your team.
DMs and private groups use MLS with a hybrid post-quantum key agreement. Plugins read messages only through grants.
Your identity is a key, not an account. A relay delivers; a host runs the workspace; the two talk over a signed protocol.
Everything a person or a plugin does is a command in one registry. Keybinds, macros, pipelines and the AI agent are all callers of that registry, so adding a plugin command gives every caller a new verb at once.
Workspaces, channels, groups and DMs, threads, attachments, reactions, search, notifications, moderation. Plain for channels; MLS-encrypted for DMs and private groups, with the server seeing only ciphertext.
Typed commands with sensitivity tiers and a confirmation gate for anything dangerous. The palette, keybinds, macros, pipelines and the agent all call it. The audit log records every run.
Client-side, sandboxed, with capability grants. Local plugins run as processes on the desktop; web plugins run in an isolated frame. A workspace can publish a signed registry of plugins for its members.
You are a key, with devices certified by it and a recovery phrase. A relay delivers to your devices; a host runs workspaces; servers prove themselves with signed descriptors and RFC 9421 request signatures.
A single server is its own relay and host. A public relay holds accounts and queues; workspaces live on hosts you or your team run.
Linux and Windows installers, with signed checksums and signed in-app updates; on macOS you build it from source for now. Point it at your own server or at a relay. The web client needs no download: your server serves it on the same domain.
AppImage, .deb and .rpm for x86_64. Your keys stay in RIM's encrypted vault, unlocked through the desktop keyring.
with the first releaseNo download until signed and notarized builds exist. Build the app from source; it runs on the Mac that built it.
built from sourceA setup .exe and an .msi for x86_64. The installers are unsigned, so SmartScreen asks once per version; the install guide shows the two clicks past it.
with the first releaseNo release exists yet. The install guide has the downloads and how to verify them, and the steps to build from source on each platform.
PostgreSQL, the server, the web client and Caddy with TLS on one domain. Backup, restore and a restore drill are scripts in the same folder.
# one server for a team cp deploy/.env.example deploy/.env # POSTGRES_PASSWORD, RIM_DOMAIN docker compose -f deploy/compose.yml up -d --build docker compose -f deploy/compose.yml exec rim \ /app/rim-server invite create # first signup link
# a public relay instead docker compose -f deploy/compose.yml \ -f deploy/compose.relay.yml up -d --build # switch modules off for this instance RIM_FEATURES_DISABLED=attachments,email
The server is configured by environment variables, all prefixed RIM_, grouped the way an operator thinks: where it listens, how people sign up, what it stores, what it talks to, what it switches off and how much it allows. Each entry: name, default, what it does, what else it needs.
| Group | Variables | Notes |
|---|---|---|
| Listening | RIM_SERVER_HOST, RIM_SERVER_PORT, RIM_PUBLIC_BASE_URL, RIM_CORS_ALLOWED_ORIGIN, RIM_TRUST_FORWARDED_HEADERS, RIM_FORCE_SECURE_COOKIES, RIM_KEY_LOGIN_AUDIENCES | A non-loopback bind needs an audience or the public URL. |
| Database and data | DATABASE_URL, RIM_DATABASE_MAX_CONNECTIONS, RIM_DATA_DIR, RIM_STORAGE_LOCAL_PATH, RIM_MAX_WORKSPACE_STORAGE_BYTES, RIM_SERVER_KEY_FILE, RIM_FRANKING_KEY_FILE | The data directory is the server's identity; back it up. |
| Signups and sessions | RIM_OPEN_REGISTRATION, RIM_REGISTRATION_POW_BITS, RIM_SIGNUP_REQUIRE_KEYS, RIM_DEFAULT_WORKSPACE, RIM_RESERVED_NAMES, RIM_PROBATION_HOURS, RIM_SESSION_TTL_DAYS, RIM_TERMS_URL, RIM_ACCOUNT_DELETION_GRACE_DAYS | Invite-only by default; invites come from the CLI. |
| Roles and federation | RIM_ROLE, RIM_HOST_ALLOWED_RELAYS, RIM_DM_INBOUND, RIM_DM_OUTBOUND, RIM_DM_PARTNERS, RIM_S2S_*, RIM_RELAY_* | relay, host or both; who may start a DM; quotas per user on a public relay. |
| RIM_SMTP_HOST, RIM_SMTP_PORT, RIM_SMTP_USERNAME, RIM_SMTP_PASSWORD, RIM_SMTP_FROM, RIM_SMTP_TLS, RIM_NOTIFICATION_DIGEST_INTERVAL_SECS | Without SMTP the email feature reports "unavailable". | |
| Operations | RIM_METRICS_ADDR, RIM_AUDIT_LOG_PATH, RIM_AUDIT_RETENTION_DAYS, RIM_FEATURES_DISABLED, RUST_LOG, RIM_ENABLE_GRAPHIQL, RIM_INSTANCE_NOTICE, RIM_LINK_PREVIEW_CONCURRENCY, RIM_PASSWORD_HASH_CONCURRENCY | Metrics on their own port; features off per instance. |
| Limits and rate limits | RIM_API_MAX_BATCH, RIM_WS_SESSION_RECHECK_SECS, RIM_RATE_* | Each rate limit is off until set. |
| Instance limits | RIM_LIMIT_* | Off until set, except pending invitations to encrypted conversations; clients read them from serverInfo.limits. |
Generated from the server's config module and the Dockerfile's variable block, so a new variable cannot ship undocumented.
Self-hosting, upgrades, backups, metrics, audit retention, feature switches, the public relay runbook.
The plugin protocol, the TypeScript SDK, the web plugin sandbox, signing a workspace plugin.
The GraphQL reference, sessions and CSRF, subscriptions over WebSocket, error codes, pagination, the file routes.
Keys and recovery, linking a device, keyboard profiles and Vim mode, encrypted conversations, relays.
Everything a client does goes through POST /api; subscriptions arrive over /api/ws. The schema is published at /api/sdl with a description on every type and field, and the reference on this site is generated from it, so it cannot drift from the server.
The whole schema, regenerated with every build and grouped by domain, each operation with its description, arguments and the error codes it can answer.
contractThe session cookie and the key-login challenge that issues it, the CSRF header on mutations, origins, the error codes in extensions.code, cursor pagination, serverInfo for version and feature negotiation.
Upload and download, avatars, plugin bundles, the signed discovery document at /.well-known/rim-server, the probes and metrics, and the server-to-server API under /s2s/v1.
What a relay can and cannot see, what a host sees, what a stolen device yields, and what a recovery phrase recovers. Written down, with the limits.
Report privately through GitHub's vulnerability reporting on the repository concerned; each repository's SECURITY.md says what to include and what response to expect.
Use whatever tools you like to write the code, including AI assistants. What we read is the pull request: it has to describe what it implements and the reason for it, in your words. A diff without that is sent back unread.
What changes, for whom, and why now. What it does not do. How you tested it, with the gate output. Which spec or doc it changes, if any.
Open an issue for anything beyond a fix, so the design is agreed first. Server features that belong to one domain are plugins, not core; the contributing guide and the roadmap say where the line is.
Formatting, lint with warnings as errors, the full test suites including the database and two-server ones, a comment style that says what code does and not its history, commits without trailers.
The server is three layers with a contract between each pair. A layer talks only to the one below it, and only through a trait. That is what lets us replace one piece, a repository on a different store, a service with different rules, a transport other than GraphQL, without touching the rest, and what lets every layer be tested against a mock of the one beneath. Pull requests that cut across the layers are sent back, however good the feature.
Domain models and repository traits sit in one crate, service traits with their commands, queries and DTOs in another. Implementations live in the infra crates. A caller imports the contract, never the implementation.
Concrete services and repositories meet in a single composition root at boot. Everything downstream holds a trait object, so swapping an implementation is a one-line change there and nowhere else.
A database error becomes a repository error, then a service error, then a GraphQL code. Each layer speaks its own error type, so a change in storage never leaks into the API contract.
No Actix, GraphQL or SeaORM types in the services or the domain. Strongly typed ids everywhere. New modules are private to their crate until something outside needs them.
Service tests mock the repositories; API tests run the schema end to end against a real database. A behaviour is pinned once, where the logic lives, with shared fixtures rather than repeated setup.
The composition root may import implementations, and non-GraphQL transports such as file upload may call service traits directly. Anything else that skips a layer is a bug, not a shortcut.
Version 0.0.1 on every crate and package. Five roadmap stages shipped: plugins, client-held history, key identity, MLS encryption, relays and hosts.
source availableThe signed updater is wired but dormant. Linux and Windows installers arrive with the first release; macOS is built from source.
no builds yetAn outside review of the identity and MLS layers is prepared, with the brief published. Results and fixes will be posted here.
pending