package rellm

Mouse Melon logoGet desktop application:
View/edit binary Protocol Buffers messages

service Rellm

rellm.proto:953

[Jump to the gRPC API?](#grpc-api) [Rellm](https://github.com/JonLatane/rellm) is a social media protocol with support for Users (and Follows), Media, Posts, Events, Groups, and Messages. It is designed to be federated, but does not require federation to be a useful next-gen forum type solution. It is designed to be used with a variety of frontends, including web, mobile, and desktop applications. It interoperates across numerous ports, protocols, and formats, including gRPC, HTTP, HTTPS, and ICS/iCal, and is designed to link with SMTP via Stalwart (and other SMTP servers/providers), Facebook Page APIs for post ing Events, and more. Essentially, your server is your own customizable, self-contained social network. Rellm is designed to be easy to run and deploy yourself with a [2 minute setup with Homebrew](#2-minute-startup-with-homebrew) and [3 minute setup on Linux](#3-minute-startup-on-linux), [images](https://hub.docker.com/r/jonlatane/rellm/tags) on [DockerHub](https://hub.docker.com/r/jonlatane/rellm_preview_generator/tags) and deployment to your K8s clusters available via a simple but powerful `Makefile`-based design language. ### Ports & Protocols Rellm servers interact across several ports: * gRPC (27707) - The main [Rellm gRPC API](#grpc-api). This is the primary port for all Rellm clients. It may or may not be TLS-enabled (443). * Clients are expected to negotiate the gRPC host via the [`backend_host` HTTP endpoint (see below)](#http-based-client-host-negotiation-for-external-cdns) on port 80/443. * HTTP (80, 8000, 27705), HTTPS (443) - The main Rellm HTTP API. This is used for some endpoints, including media upload/download, and for negotiating the gRPC host. * [Public HTTP Endpoints](#external-http-servers-80-8000-443) and the Elm, React and Flutter [Web UI](#web-ui-paths)s are served up on multiple ports: * Port 443 will serve up a secure HTTPS server. If it fails to startup, Rellm handles this gracefully and degrades to plain HTTP. * Port 80 will serve up either an unsecured set of Rellm's HTTP endpoints, or a redirect to the HTTPS/443 server if that one launched successfully. * Port 8000 *always* serves up an unsecured Rellm UI, in case something goes horribly wrong with 80 and 443. It can probably not be exposed in your load balancer/to the web. * Port 27705 is a [private, unsecured HTTP server](#internal-http-server-27705) meant for communication with other non-web facing services on your computer or in your cluster. It should not be exposed to the web. * Currently this just has an `/email` endpoint. It is designed for [email/SMTP support via an integration with Stalwart](https://github.com/JonLatane/rellm/tree/main/deploys/email). #### Cross-Protocol Federation Rellm clients can translate content from other federated protocols into the same [`Post`](#rellm-Post)/[`Author`](#rellm-Author) shapes used everywhere else in the app -- entirely client-side, with no RPCs of their own. The server's only role is admin configuration: [`FederationInfo`](#rellm-FederationInfo) tells clients which instances/apps are safe or expected to pull from. There is no server-to-server proxying or bridging involved - this follows the same "the client does the merging" pattern as [`FederatedServer`](#rellm-FederatedServer), just reaching across a protocol boundary instead of a Rellm-to-Rellm one. It's also one-directional (reading in, not posting out) - publishing a Rellm [`Post`](#rellm-Post) *to* Mastodon or Bluesky is a separate feature, [`SyncDestination`](#rellm-SyncDestination). ##### Mastodon/ActivityPub A client can browse any Mastodon instance's local public timeline (`GET /api/v1/timelines/public?local=true`) with zero configuration, since it's already a public, unauthenticated REST endpoint - no [`MastodonServer`](#rellm-MastodonServer) entry is needed just to *read* public posts. Connecting an actual Mastodon *account* is a heavier flow, since Mastodon has no single central OAuth authority the way Facebook/X do - every instance is its own separate OAuth provider. A server admin registers an app on a given instance ahead of time (`FederationInfo.mastodon_servers`, a [`MastodonServer`](#rellm-MastodonServer) carrying that instance's `app_id`/`app_secret`), and only then can a user on that instance complete the OAuth popup + PKCE flow to connect their own account. `MastodonServer.configured_by_default`/ `pinned_by_default` let an admin recommend a given instance be auto-browsed the first time a client visits this server - see those fields' own docs for the current relationship between the two. ##### BlueSky/AT Protocol AT Protocol splits "a server" into roles: a Personal Data Server (PDS) hosts an account's data and handles its login, while an AppView serves the read side (feeds, threads, profiles) for the whole network. Like Mastodon's public timelines, a client can browse public Bluesky data with no account at all by reading from an AppView such as `public.api.bsky.app` (unauthenticated, CORS-enabled) - an admin can recommend one via [`BlueskyServer`](#rellm-BlueskyServer) (`FederationInfo.bluesky_servers`). What an anonymous reader can't get is anything personalized, like a home timeline. Connecting a Bluesky *account* additionally unlocks that: a handle and an [App Password](https://bsky.app/settings/app-passwords) (not OAuth -- AT Protocol has no per-client app-registration step the way Mastodon/Facebook/X require), used to call `com.atproto.server.createSession` on the account's *own* PDS (found by resolving the handle to a DID and reading the DID document's `#atproto_pds` service - it isn't necessarily `bsky.social`) and then the account's own `app.bsky.feed.getTimeline`. Because there's no server-side app to register, nothing about connecting an account is admin-configurable the way a Mastodon OAuth app is. Clients show at most one Bluesky source at a time (an AppView *or* an account), since both read the same global network. ### API Design Notes #### Moderation and Visibility Rellm APIs are designed to support [`Moderation`](#rellm-Moderation) and [`Visibility`](#rellm-Visibility) controls at the level of individual entities. However, to keep things DRY, moderation and visibility controls are only implemented for [`User`](#rellm-User)s, [`Media`](#rellm-Media), [`Group`](#rellm-Group)s, and [`Post`](#rellm-Post)s. [`Event`](#rellm-Event)s and future [`Post`](#rellm-Post)-like types simply use the same implementation as their contained [`Post`](#rellm-Post)s. The intent here is to maximize both shared code and implementation robustness. #### Composition Over Inheritance Rellm's APIs are designed using composition over inheritance. For instance, an [`Event`](#rellm-Event) contains a [`Post`](#rellm-Post) rather than extending it. This pattern fits well all the way from the data model (very boring, safe, and normalized), through Rust code implementing APIs, to both functional React code and more-OOP Flutter code equally well. #### Predictable Atomicity The use of composition over inheritance also means that Rellm APIs can be *predictably* non-atomic based on their compositional structure. For instance, [`UpdatePost`](#grpc-api-UpdatePost) is fully atomic. [`UpdateEvent`](#grpc-api-UpdateEvent), however, is non-atomic. Given that an [`Event`](#rellm-Event) has a [`Post`](#rellm-Post) and many [`Occasion`](#rellm-Occasion)s, [`UpdateEvent`](#grpc-api-UpdateEvent) is implemented as a composition of four other RPCs - each independently callable and individually atomic -- run in a fixed order: [`UpdateEventDetails`](#grpc-api-UpdateEventDetails) (which itself first updates the [`Event`](#rellm-Event)'s own [`Post`](#rellm-Post) atomically, literally calling the [`UpdatePost`](#grpc-api-UpdatePost) RPC), then [`CreateNewOccasions`](#grpc-api-CreateNewOccasions), [`UpdateOccasions`](#grpc-api-UpdateOccasions), and finally [`DeleteRemovedOccasions`](#grpc-api-DeleteRemovedOccasions). Create must run before Delete so that a request which both drops an old [`Occasion`](#rellm-Occasion) and adds a new one never transiently leaves the [`Event`](#rellm-Event) with zero Occasions. Because moderation/visibility lives at the [`Post`](#rellm-Post) level, and [`UpdateEventDetails`](#grpc-api-UpdateEventDetails) runs first, this means that a developer error in the later [`Occasion`](#rellm-Occasion)-processing steps cannot prevent visibility and moderation changes from being made in Events, even if there are errors elsewhere. This should prove a robust pattern for any future entities intended to be shareable at a Group level with visibility and moderation controls (for instance, `Sheet`, `SharedExpenseReport`, `SharedCalendar`, etc.). The entire architecture should promote this approach to predictable atomicity. ### Core Types Rellm's data model centers around a handful of top-level types, most of which carry their own [`Visibility`](#rellm-Visibility) and [`Moderation`](#rellm-Moderation) state and can be organized into [`Group`](#rellm-Group)s. #### ServerConfiguration Rellm incorporates server configuration, including fairly deep customization of the end-user UI/UX, as perhaps its *most* primitive type. [`ServerConfiguration`](#rellm-ServerConfiguration) is unlike most of the highly-normalized, minimalist types in the Rellm protocol, and is more like a document than a row in a database. (That said, every [`ServerConfiguration`](#rellm-ServerConfiguration) change *is* a row in a database, meaning reverting broken configurations is easy.) Any client using the Rellm protocol is basically expected to follow a flow of "get service version, then [`ServerConfiguration`](#rellm-ServerConfiguration), then worry about auth, then finally about retrieving anything else." ##### Server Info and Theme [`ServerInfo`](#rellm-ServerInfo) (`server_info`) carries the server's public-facing identity: `name`, `short_name`, `description`, `privacy_policy` and `media_policy` text shown during account creation and on the `/about` page, a multi-size [`ServerLogo`](#rellm-ServerLogo) (separate light/dark, square/wide media IDs), a [`ServerColors`](#rellm-ServerColors) scheme (primary/navigation accents plus author/admin/moderator name colors), and `web_user_interface` choosing which UI a browser is served (React/Tamagui by default, or the Elm SPA/Flutter Web alternatives). ##### Custom Tabs [`CustomNavigationTabSet`](#rellm-CustomNavigationTabSet) (`custom_tabs`) lets a server admin override the Elm UI's default navigation. `home` (a [`CustomHomePage`](#rellm-CustomHomePage)) replaces `/` itself - a predefined tab or a specific Post, optionally with Posts pinned above its content and/or an Events strip shown above it; `tabs` (repeated [`CustomNavigationTab`](#rellm-CustomNavigationTab)) replaces the `EVENTS_TAB`/`POSTS_TAB`/`PEOPLE_TAB`/`ABOUT_TAB` set entirely, each pinned to its own custom URL (`path`). Each `CustomNavigationTab` targets either a predefined [`NavigationTab`](#rellm-NavigationTab), a Post ID, or (path-only) a user profile, with its own emoji- or Media-backed icon and optional title override. `path` is fully live - the Elm SPA actually routes it (`Pages.UsernameOrCustomTab_`), not just previews it - except for the built-in `/events`, `/posts`, `/people`, and `/about` paths themselves, which stay reserved for their own matching predefined tab and can't be remapped elsewhere. ##### Anonymous, Default, and Basic User Permission Sets Three [`Permission`](#rellm-Permission) lists set the server's baseline access, each enforced independently of any per-User/per-Group grants: `anonymous_user_permissions` (what a logged-out visitor may do - only the `VIEW_*` permissions are valid here), `default_user_permissions` (what every new account starts with), and `basic_user_permissions` (the superset a user holding `GRANT_BASIC_PERMISSIONS` may hand out to others). Granting `GLOBAL_PUBLIC` as a feature's `default_visibility` (see `people_settings`/`group_settings`/`post_settings`/ `event_settings` below) requires the matching `PUBLISH_*_GLOBALLY` permission to actually be present in `default_user_permissions`. ##### Federation Settings [`FederationInfo`](#rellm-FederationInfo) (`federation_info`) is where all federation and social-sync credentials live. ###### Other Rellm servers `servers` (repeated [`FederatedServer`](#rellm-FederatedServer)) recommends other Rellm hosts to clients, each optionally `configured_by_default` (client should enable/configure it automatically) and/or `pinned_by_default` (client should pin its Events/Posts alongside the "main" server's). ###### Mastodon/ActivityPub servers `mastodon_servers` (repeated [`MastodonServer`](#rellm-MastodonServer)) plays a similar role to `servers` above, but for Mastodon instances instead of other Rellm servers - see [Cross-Protocol Federation](#cross-protocol-federation) for the client-side feature this backs. Unlike a real `FederatedServer`, though, an entry here is *not* required just to browse an instance's public timeline read-only - that's already a public, unauthenticated Mastodon REST endpoint any client can call directly. It's only needed to let a user *connect their own* Mastodon account (OAuth + PKCE), since Mastodon has no single central OAuth authority the way Facebook/X do: every instance is its own separate OAuth provider, so an admin has to register an app (`app_id`/`app_secret`, the latter *never* serialized to the client) on each instance individually before its users can connect. `configured_by_default`/`pinned_by_default` mirror `FederatedServer`'s own fields, but govern that anonymous browsing instead: whether clients should auto-add the instance to their browsed list the first time they visit this server, not whether an account gets auto-connected (that always requires the user's own explicit OAuth consent). A Mastodon instance functions like a much thinner version of a federated Rellm server in the UI: its public posts appear in the same multi-server feed, translated into Rellm's own [`Post`](#rellm-Post) shape, but it has no equivalent of Rellm's Events, Groups, Media library, or People/Follows - just posts and their authors. ###### Facebook API Keys `facebook_auth_config` (a [`FacebookAuthConfig`](#rellm-FacebookAuthConfig), `app_id`/`app_secret`) registers this server's Facebook App, enabling users to connect Facebook Page and Instagram Business [`SyncDestination`](#rellm-SyncDestination)s. `app_secret` is write-only/never serialized back to clients; admins set/rotate it via [`ConfigureServer`](#grpc-api-ConfigureServer) (i.e. the same admin UI form that manages the rest of [`ServerConfiguration`](#rellm-ServerConfiguration)) - the secret is simply never echoed back in subsequent [`GetServerConfiguration`](#grpc-api-GetServerConfiguration) responses. ###### X (Twitter) API Keys `x_twitter_auth_config` (an [`XTwitterAuthConfig`](#rellm-XTwitterAuthConfig), `client_id`/`client_secret`) registers this server's X Developer App, enabling users to connect X [`SyncDestination`](#rellm-SyncDestination)s - until set, X SyncDestinations fail with `x_twitter_app_not_configured`. `client_secret` is write-only/never serialized back to clients, set/rotated the same way as the Facebook API keys above. ##### Web Push Configuration [`WebPushConfig`](#rellm-WebPushConfig) (`web_push_config`) holds the server's VAPID keypair for Web Push notifications: `public_vapid_key` is served to clients so they can subscribe, while `private_vapid_key` signs outgoing pushes and is *never* serialized to clients - like the federation secrets above, admins set/rotate it via [`ConfigureServer`](#grpc-api-ConfigureServer), not by editing the database directly. ##### CDN Configuration [`ExternalCDNConfig`](#rellm-ExternalCDNConfig) (`external_cdn_config`) enables running Rellm behind a CDN (e.g. Cloudflare's "CNAME HTTPS Proxy"): when set, the unsecured HTTP server (port 80) stops redirecting to HTTPS and instead serves the Tamagui Web client directly, with `frontend_host`/`backend_host` telling the web client which domains to use instead of `window.location.hostname` (Tamagui web only, for now). `secure_media` plus its `media_ipv4_allowlist`/`media_ipv6_allowlist` are a (TODO, not yet enforced) way to restrict media downloads on the unsecured server to the CDN's own IP ranges; `cdn_grpc` is a further (TODO) mode that would move the gRPC server itself onto port 443 to ride along Cloudflare's gRPC support. #### User A [`User`](#rellm-User) is a Rellm account: username, real name, bio, avatar, contact methods, and [`Permission`](#rellm-Permission)s, plus counts (followers, posts, events, etc.) and federation info (see [Federated Profiles](#federated-profiles) above). A lighter-weight [`Author`](#rellm-Author) (just ID, username, avatar, real name, permissions) is embedded on [`Post`](#rellm-Post)s, [`Message`](#rellm-Message)s, and similar content types instead of a full [`User`](#rellm-User), to keep those payloads small. ##### Follows A [`Follow`](#rellm-Follow) is one [`User`](#rellm-User) following another, optionally subject to the target's moderation (i.e. approval). Mutual follows make two users "friends." Follows also drive the `FOLLOWING_POSTS`/`FOLLOWING_EVENTS` listing types and `LIMITED`-visibility content. ##### Memberships A [`Membership`](#rellm-Membership) is a [`User`](#rellm-User)'s membership (or pending join request/invitation) in a [`Group`](#rellm-Group), tracking the user's [`Permission`](#rellm-Permission)s within the group plus separate group-side and user-side [`Moderation`](#rellm-Moderation) (for join-approval flows). Returned as part of [`User`](#rellm-User)/[`Group`](#rellm-Group) payloads, and via [`Member`](#rellm-Member) when listing a Group's members. ##### SyncSources While Federation is a first-class feature of Rellm, a [`User`](#rellm-User) can also own many [`SyncSource`](#rellm-SyncSource)s - server-owned external origins to sync [`Post`](#rellm-Post)s in from other fediverse and less-open platforms, via a `oneof configuration` naming which source type it is: an iCal subscription URL (`configuration.ics_subscription_url`, syncing in [`Event`](#rellm-Event)s/ [`Occasion`](#rellm-Occasion)s), or an RSS/Atom subscription URL (`configuration.rss_subscription_url`/ `configuration.atom_subscription_url`, syncing in plain [`Post`](#rellm-Post)s). Every kind of synced content is tagged via its own `Post.sync_source` - an [`Event`](#rellm-Event)'s own Post, each of its [`Occasion`](#rellm-Occasion)s' own Post, or a plain synced Post - since a single source can back many synced Posts but each Post has at most one source it came from; see the Event and Post sections below for how these attach. A background job re-pulls each source on its own `sync_interval_seconds` cadence, recomputing `event_count`/`occasion_count` (iCal) or `post_count` (RSS/Atom) on every sync. Sources are managed via [`GetSyncSources`](#grpc-api-GetSyncSources), [`CreateSyncSource`](#grpc-api-CreateSyncSource) (requires `SYNC_EVENTS_FROM_ICS`/`SYNC_POSTS_FROM_RSS`/`SYNC_POSTS_FROM_ATOM` - whichever matches the source's own configuration - or Admin), [`UpdateSyncSource`](#grpc-api-UpdateSyncSource), and [`DeleteSyncSource`](#grpc-api-DeleteSyncSource). See also: [`SyncDestination`](#rellm-SyncDestination) ###### iCal `configuration.ics_subscription_url` is a plain iCal (`.ics`) subscription URL. The background job fetches and parses it on each sync, creating/updating one [`Event`](#rellm-Event) (and one [`Occasion`](#rellm-Occasion) per occurrence) per iCal `VEVENT` - each occurrence's own Post is keyed by `(sync_source_id, sync_source_uid, sync_source_recurrence_anchor)`, the iCal UID plus that occurrence's stable identity within its series (its own start time, or its original scheduled time if since rescheduled) - and recomputing `event_count`/ `occasion_count`. An `Occasion`'s `sync_missing_since` is set the first time it stops appearing in the feed, letting the owner decide whether that means it should be deleted. No auth/credentials are supported yet - only public iCal URLs. ###### RSS `configuration.rss_subscription_url` is a plain RSS 2.0 subscription URL. The background job fetches and parses it on each sync, creating/updating one plain [`Post`](#rellm-Post) per RSS `<item>` - each Post is keyed by `(sync_source_id, sync_source_uid)`, `sync_source_uid` being the item's own `<guid>` (or a hash of its `<link>` if it has none) - and recomputing `post_count`. Unlike iCal, a `<item>` that stops appearing in the feed is left alone rather than pruned: RSS feeds are commonly truncated to their most recent N items by the publisher, so "no longer in the feed" doesn't mean "was retracted". No auth/credentials are supported yet - only public RSS URLs. ###### Atom `configuration.atom_subscription_url` is a plain Atom subscription URL, behaving identically to RSS (above) -- one plain [`Post`](#rellm-Post) per `<entry>`, keyed by `(sync_source_id, sync_source_uid)` with `sync_source_uid` being the entry's own `<id>`, recomputing `post_count`, missing entries left alone rather than pruned. RSS and Atom feeds are parsed via the same underlying library into one unified shape, so both formats share this exact behavior - pick whichever a given source actually publishes. ##### SyncDestinations A [`User`](#rellm-User) can also own many [`SyncDestination`](#rellm-SyncDestination)s - user-owned external targets to push [`Occasion`](#rellm-Occasion)s and [`Post`](#rellm-Post)s out to (see the Event and Post sections below for how these attach), via a `oneof configuration` naming which platform it is. This is a many-to-many relationship: it's each [`Occasion`](#rellm-Occasion) or [`Post`](#rellm-Post) (not, say, the parent [`Event`](#rellm-Event)) that syncs out, and each may push to several destinations at once, tracked per-destination via the repeated `Occasion.sync_destinations`/`Post.sync_destinations` (each a [`SyncDestinationStatus`](#rellm-SyncDestinationStatus), carrying the destination's resulting post ID/URL and last-synced time). Destinations are pushed to on demand rather than synced in bulk on an interval, so `synced_occasion_count`/`synced_post_count` are computed with a `COUNT` at request time instead of being recomputed-and-stored. All API keys for these external platforms are stored in [`ServerConfiguration`](#rellm-ServerConfiguration)'s `federation_info`. Destinations are managed via [`GetSyncDestinations`](#grpc-api-GetSyncDestinations), [`CreateSyncDestination`](#grpc-api-CreateSyncDestination), [`UpdateSyncDestination`](#grpc-api-UpdateSyncDestination), and [`DeleteSyncDestination`](#grpc-api-DeleteSyncDestination) - each gated on the `SYNC_EVENTS_TO_*`/ `SYNC_POSTS_TO_*` permission pair matching the destination's own platform (or Admin; see each platform's own section below). Actually syncing (or un-syncing) a given [`Occasion`](#rellm-Occasion) or [`Post`](#rellm-Post) to a destination is a separate step, via [`SyncOccasion`](#grpc-api-SyncOccasion)/ [`DeleteOccasionSyncDestination`](#grpc-api-DeleteOccasionSyncDestination) and [`SyncPost`](#grpc-api-SyncPost)/[`DeletePostSyncDestination`](#grpc-api-DeletePostSyncDestination), gated the same way (the `_EVENTS_`/`_POSTS_` half matching which RPC). See also: [`SyncSource`](#rellm-SyncSource) ###### Facebook `configuration.facebook_page` (a [`FacebookPage`](#rellm-FacebookPage)) is a connected Facebook Page. Connecting one requires a short-lived user access token from client-side Facebook Login (`FacebookPage.short_lived_user_access_token`), which the server exchanges for a long-lived Page access token; the short-lived token is write-only and never populated back in responses. Gated on `SYNC_EVENTS_TO_FACEBOOK`/ `SYNC_POSTS_TO_FACEBOOK`. ###### Instagram `configuration.instagram_account` (an [`InstagramAccount`](#rellm-InstagramAccount)) is a connected Instagram Business/Creator account. Instagram posting is only possible for an account linked to a Facebook Page, so connecting one reuses the exact same Facebook Login flow/app credentials as Facebook above - the server exchanges the token for the chosen Page's access token, then looks up that Page's linked Instagram Business account (`instagram_business_account_id`). Unlike Facebook, Instagram's Graph API has no text-only post type; syncing a [`Post`](#rellm-Post)/[`Occasion`](#rellm-Occasion) with no attached media fails with `instagram_requires_media`. Gated on `SYNC_EVENTS_TO_INSTAGRAM`/`SYNC_POSTS_TO_INSTAGRAM`. ###### Mastodon `configuration.mastodon_account` (a [`MastodonAccount`](#rellm-MastodonAccount)) is a connected Mastodon account, on any instance the user names (`instance_host`) - there's no single app to register the way Facebook/Instagram have one, so connecting one is a user-pasted Personal Access Token (`MastodonAccount.access_token`, generated on the user's own instance under Preferences > Development) rather than an OAuth popup. Gated on `SYNC_EVENTS_TO_MASTODON`/`SYNC_POSTS_TO_MASTODON`. ###### Bluesky `configuration.bluesky_account` (a [`BlueskyAccount`](#rellm-BlueskyAccount)) is a connected Bluesky (AT Protocol) account. Connecting one is a user-supplied "App Password" (`BlueskyAccount.app_password`, generated at Settings > App Passwords - not the account's main password) rather than an OAuth popup. Gated on `SYNC_EVENTS_TO_BLUESKY`/`SYNC_POSTS_TO_BLUESKY`. ###### X (Twitter) `configuration.x_twitter_account` (an [`XTwitterAccount`](#rellm-XTwitterAccount)) is a connected X account. Requires this server to have a registered X Developer App configured (`FederationInfo.x_twitter_auth_config`) - until an admin sets one, every RPC touching an [`XTwitterAccount`](#rellm-XTwitterAccount) destination fails with `x_twitter_app_not_configured`. Once configured, connecting is an OAuth 2.0 Authorization Code + PKCE flow at x.com (`response_type=code`, like Threads, but with a `code_challenge`/`code_verifier` pair X requires and Threads doesn't) - the server exchanges the code for a short-lived access token (2 hour expiry) plus a refresh token, transparently refreshing before each post. Only image media is uploaded today; video is not yet supported (see `XTwitterAccount`'s own doc). Gated on `SYNC_EVENTS_TO_X_TWITTER`/`SYNC_POSTS_TO_X_TWITTER`. ###### Threads `configuration.threads_account` (a [`ThreadsAccount`](#rellm-ThreadsAccount)) is a connected Threads account. Threads API is a product added to this server's *existing* Facebook App (see [`FacebookAuthConfig`](#rellm-FacebookAuthConfig)) rather than a separately-registered app, but its OAuth flow is otherwise its own: authorization happens at threads.net (not facebook.com) using `response_type=code` rather than Facebook's implicit `response_type=token`, with no "choose a Page" step - it directly authorizes the user's own Threads account. The server exchanges the code for a short-lived token, then a long-lived one (~60 day expiry, refreshable via `grant_type=th_refresh_token` - not yet implemented, so a connected destination needs reconnecting after ~60 days). Unlike Instagram, Threads supports text-only posts. Gated on `SYNC_EVENTS_TO_THREADS`/`SYNC_POSTS_TO_THREADS`. ##### AIProviders A [`User`](#rellm-User) can also own many [`AIProvider`](#rellm-AIProvider)s - connections to external AI model APIs (e.g. a Gemini or OpenAI API key) - and grant other users metered access to them via [`AIProviderGrant`](#rellm-AIProviderGrant)s. See `ai_providers.proto` and the AIProvider section below. Which models are actually available, and what each can do ([`AIModelCapability`](#rellm-AIModelCapability)), is a hand-maintained catalog (no provider exposes a stable "list models" API to build this from at request time) - see [`backend/src/logic/ai_model_catalog.rs`](https://github.com/JonLatane/rellm/blob/main/backend/src/logic/ai_model_catalog.rs) on GitHub for the actual source of truth. #### AIProvider An [`AIProvider`](#rellm-AIProvider) is a user-owned connection to an external AI model API (e.g. a Gemini API key), via a `oneof provider` naming which service it is - structurally similar to [`SyncDestination`](#rellm-SyncDestination)/[`SyncSource`](#rellm-SyncSource), but rather than pushing/pulling content, it's metered *access* an owner can share out to other users of this server. As with [`SyncDestination`](#rellm-SyncDestination)'s platform credentials, the actual API key is write-only - accepted on [`CreateAIProvider`](#grpc-api-CreateAIProvider)/[`UpdateAIProvider`](#grpc-api-UpdateAIProvider) but never populated back in a response. Providers are managed via [`GetAIProviders`](#grpc-api-GetAIProviders), [`CreateAIProvider`](#grpc-api-CreateAIProvider) (requires `CREATE_AI_PROVIDERS`, or Admin), [`UpdateAIProvider`](#grpc-api-UpdateAIProvider), and [`DeleteAIProvider`](#grpc-api-DeleteAIProvider) - each gated self-or-Admin, the same shape as [`SyncDestination`](#rellm-SyncDestination)'s RPCs. ##### Gemini `provider.gemini_credentials` (a [`GeminiCredentials`](#rellm-GeminiCredentials)) is a Google Gemini API connection (`ai.google.dev/gemini-api`), used for image generation/editing (e.g. generating Event posters) via its Interactions API. ##### OpenAI `provider.openai_credentials` (an [`OpenAICredentials`](#rellm-OpenAICredentials)) is an OpenAI API connection (`platform.openai.com/docs/guides/image-generation`), used for image generation/editing via its Images API (GPT Image models). ##### Anthropic `provider.anthropic_credentials` (an [`AnthropicCredentials`](#rellm-AnthropicCredentials)) is reserved for a connected Anthropic API, but **not yet creatable** - Anthropic doesn't offer an image generation API, so it's defined only for forward compatibility. ##### DigitalOcean `provider.digitalocean_credentials` (a [`DigitalOceanCredentials`](#rellm-DigitalOceanCredentials)) is a DigitalOcean Gradient AI Platform / Serverless Inference connection (`docs.digitalocean.com/products/inference`), used for image *generation only* (no editing - DigitalOcean's Serverless Inference API has no `/v1/images/edits`-equivalent endpoint) via its OpenAI-Images-API-shaped `/v1/images/generations` endpoint (GPT Image and Stable Diffusion models, re-hosted under DigitalOcean's own billing). ##### AIProviderGrants A provider's owner may share metered access to it with other users via [`AIProviderGrant`](#rellm-AIProviderGrant)s, each carrying a `tokens_remaining` budget for that grantee. Granted/reset via [`GrantAIProvider`](#grpc-api-GrantAIProvider) (upserted on the unique `(ai_provider_id, grantee)` pair - granting again *resets*, rather than adds to, `tokens_remaining`) and removed via [`RevokeAIProvider`](#grpc-api-RevokeAIProvider). Unlike every other RPC pair in this section, these two are **owner-only, with no Admin override** - an Admin may manage the provider record itself, but only its owner may hand out access to it. #### Rellm's Market Rellm's Market (`market.proto`) is this server's storefront - a small, Stripe-backed marketplace an admin stocks with up to twelve [`MarketProduct`](#rellm-MarketProduct)s (one offering type times one billing period each) that any user can buy. Four offering types exist today ([`PurchaseType`](#rellm-PurchaseType)): `PURCHASE_TYPE_MEDIA_STORAGE` (raises the buyer's `User.media_storage_limit_bytes`), `PURCHASE_TYPE_AI_GRANTS` (grants/resets an [`AIProviderGrant`](#rellm-AIProviderGrant) against one of the server operator's [`AIProvider`](#rellm-AIProvider)s), `PURCHASE_TYPE_RELLM_HOSTING` (bills the buyer for the server operator to stand up a new Rellm instance on a domain of their choosing - provisioned by hand, not automated; the admin's own `/market/fulfillment` page, backed by `GET_MARKET_SUBSCRIPTIONS_REQUEST_FOR_FULFILLMENT_ADMIN` and [`UpdateMarketSubscription`](#grpc-api-UpdateMarketSubscription), tracks these orders - [`RellmHostingSubscriptionDetails`](#rellm-RellmHostingSubscriptionDetails)'s own `fulfillment_status`/ `fulfillment_notes` fields), and `PURCHASE_TYPE_PERMISSIONS_ACCESS` (grants the buyer a fixed set of `Permission`s, e.g. pay-gating `SYNC_EVENTS_TO_FACEBOOK`). Every purchase gets a [`MarketSubscription`](#rellm-MarketSubscription) -- even a one-time ([`PurchasePeriod`](#rellm-PurchasePeriod) `PURCHASE_PERIOD_INDEFINITE`) purchase, so it's still cancelable and still carries the same per-type fulfillment tracking (e.g. `fulfillment_status`/`fulfillment_notes` above) a recurring one does; it just never gets a `renews_at`, so it never actually bills again. A recurring `PURCHASE_PERIOD_ANNUAL`/`PURCHASE_PERIOD_MONTHLY` subscription's `billing_history` accumulates one [`MarketPurchase`](#rellm-MarketPurchase) per renewal; an indefinite one's stays at exactly one. Products are listed via [`GetMarketProducts`](#grpc-api-GetMarketProducts) (unauthenticated; admins additionally see delisted ones) and managed via [`CreateMarketProduct`](#grpc-api-CreateMarketProduct)/[`UpdateMarketProduct`](#grpc-api-UpdateMarketProduct) (both Admin-only; a [`MarketProduct`](#rellm-MarketProduct)'s `type`/`period` are immutable once created). Buying one is a two-step, webhook-settled flow: [`MakeMarketPurchase`](#grpc-api-MakeMarketPurchase) starts a Stripe Checkout Session and returns its URL to redirect the buyer to - no [`MarketPurchase`](#rellm-MarketPurchase)/[`MarketSubscription`](#rellm-MarketSubscription) is created yet, so an abandoned checkout leaves nothing behind. Only once Stripe confirms payment (a `checkout.session.completed` webhook delivery) does the server create the [`MarketPurchase`](#rellm-MarketPurchase)/[`MarketSubscription`](#rellm-MarketSubscription) and apply its entitlement. A recurring [`MarketSubscription`](#rellm-MarketSubscription) renews itself thereafter via off-session charges against the payment method saved on that first checkout - no further action from the buyer - until either a renewal charge fails or the buyer/admin calls [`CancelMarketSubscription`](#grpc-api-CancelMarketSubscription), either of which sets `canceled_at`. The entitlement itself stays in effect until whichever is later of `renews_at`/`canceled_at` - once both have passed, the `renew_market_subscriptions` background job revokes it (reverts `media_storage_limit_bytes` to `ServerConfiguration.media_settings.default_user_media_allocation_bytes`, or removes the granted `Permission`s, depending on `type`) and sets `service_terminated_at`. A user's own MarketSubscriptions (never anyone else's) are listed via [`GetMarketSubscriptions`](#grpc-api-GetMarketSubscriptions), and also travel along on [`User`](#rellm-User) itself (`User.market_subscriptions`) the same way `ai_models`/`sync_sources` do. Stripe credentials for all of this live in `ServerConfiguration.stripe_config` (a [`StripeConfig`](#rellm-StripeConfig)), Admin-only like `twilio_config`/`bird_config`. #### Media [`Media`](#rellm-Media) represents an uploaded (or server-generated) photo or video. Unlike other types, Media content itself is *not* served over gRPC - it's uploaded/downloaded via plain HTTP (`POST`/`GET /media`) - while its metadata (content type, name, visibility, moderation) is managed like any other Rellm type. Other messages (like `User.avatar`, `Group.avatar`, and `Post.media`) reference Media via the lightweight [`MediaReference`](#rellm-MediaReference) type. #### Post [`Post`](#rellm-Post) is Rellm's fundamental content/building-block type: it's what actually carries a title/link/content body, visibility, and moderation, and is reused (via [`PostContext`](#rellm-PostContext)) as the backing data for replies, [`Event`](#rellm-Event)s, and [`Occasion`](#rellm-Occasion)s alike. Posts can be replied to (threaded via `reply_to_post_id`), cross-posted to [`Group`](#rellm-Group)s ([`GroupPost`](#rellm-GroupPost)), and shared directly with users ([`UserPost`](#rellm-UserPost)). ##### GroupPosts A [`GroupPost`](#rellm-GroupPost) is the cross-posting of a [`Post`](#rellm-Post) into a [`Group`](#rellm-Group), carrying the group-specific moderation status and who shared it, separately from the Post's own (author-set) visibility/moderation. ##### UserPosts A [`UserPost`](#rellm-UserPost) is a "direct share" of a [`Post`](#rellm-Post) to a [`User`](#rellm-User) (see also `DIRECT` [`Visibility`](#rellm-Visibility)). Currently unused/unimplemented. ##### SyncDestinations A [`Post`](#rellm-Post) may also be synced (cross-posted) out to a user-owned [`SyncDestination`](#rellm-SyncDestination) (e.g. a connected Facebook Page), the same mechanism [`Occasion`](#rellm-Occasion)s use (see below) - each Post may push to several destinations at once, tracked via the repeated `Post.sync_destinations` (each a [`SyncDestinationStatus`](#rellm-SyncDestinationStatus)). #### Event An [`Event`](#rellm-Event) is a wrapper for *at least two* [`Post`](#rellm-Post)s. It always has its own top-level [`Post`](#rellm-Post) (`PostContext.EVENT`, holding the event's overall title/description) *and* it must have at least one [`Occasion`](#rellm-Occasion) (see below), each of which in turn must have its own [`Post`](#rellm-Post) (`PostContext.OCCASION`, carrying that Occasion's start/end time, [`Location`](#rellm-Location), and optional per-Occasion title/link/content override). So the smallest possible Event already backs 2 Posts, and events with recurring/multiple Occasions back one Post per Occasion beyond that. ##### Occasions An [`Occasion`](#rellm-Occasion) is the actual time-boxed occurrence of an [`Event`](#rellm-Event) - it carries the `starts_at`/`ends_at` timestamps and optional [`Location`](#rellm-Location) that the parent [`Event`](#rellm-Event) itself does not have. An [`Event`](#rellm-Event) with zero Occasions is meaningless (no time or place to attach to), so every [`Event`](#rellm-Event) must have at least one. - **Rsvps**: An [`Rsvp`](#rellm-Rsvp) tracks one attendee's status (`INTERESTED`, `REQUESTED`, `GOING`, `NOT_GOING`) for a specific [`Occasion`](#rellm-Occasion). Attendees may be logged-in [`User`](#rellm-User)s or anonymous (tracked via [`AnonymousAttendee`](#rellm-AnonymousAttendee) plus an `auth_token`), and are subject to their own [`Moderation`](#rellm-Moderation), independent of the Event's/Occasion's own Post moderation. - **SyncSource**: It's actually the parent [`Event`](#rellm-Event) (not the [`Occasion`](#rellm-Occasion)) that can be synced *in* from a user-owned [`SyncSource`](#rellm-SyncSource) (e.g. an iCal subscription). The relationship is 1:(0 or 1): a single source can back many synced [`Event`](#rellm-Event)s, but each [`Event`](#rellm-Event) has *at most one* source it came from (`Event.sync_source` is a single optional field, not repeated). - **SyncDestinations**: Conversely, it's each [`Occasion`](#rellm-Occasion) (not the parent [`Event`](#rellm-Event)) that syncs *out* to [`SyncDestination`](#rellm-SyncDestination)s (e.g. connected Facebook Pages) - the same mechanism [`Post`](#rellm-Post)s use (see above). Unlike [`SyncSource`](#rellm-SyncSource), this is the outlier's counterpart - a many-to-many relationship: each Occasion may push to several destinations at once, tracked per-destination via the repeated `Occasion.sync_destinations` (each a [`SyncDestinationStatus`](#rellm-SyncDestinationStatus)), carrying the destination's resulting post ID/URL and last-synced time. #### Group A [`Group`](#rellm-Group) organizes [`User`](#rellm-User)s, [`Post`](#rellm-Post)s, and [`Event`](#rellm-Event)s together under shared visibility, moderation, and permission defaults. ##### Memberships A [`Membership`](#rellm-Membership) is a [`User`](#rellm-User)'s membership (or pending join request/invitation) in a [`Group`](#rellm-Group), tracking the user's [`Permission`](#rellm-Permission)s within the group plus separate group-side and user-side [`Moderation`](#rellm-Moderation) (for join-approval flows). Returned as part of [`User`](#rellm-User)/[`Group`](#rellm-Group) payloads, and via [`Member`](#rellm-Member) when listing a Group's members. ##### GroupPosts A [`GroupPost`](#rellm-GroupPost) is the cross-posting of a [`Post`](#rellm-Post) into a [`Group`](#rellm-Group), carrying the group-specific moderation status and who shared it, separately from the Post's own (author-set) visibility/moderation. #### Message [`Message`](#rellm-Message) is Rellm's "low trust" messaging/email system, meant to let strangers on a server make first contact (e.g. via email, with no account required) before moving to a more trusted channel. Admins have open access to all Messages on a server. Email support in Messages comes from the [Stalwart integration](#post-email-stalwart-email-integration) and requires a Stalwart server to be running and configured to forward emails to the Rellm server. Rellm provides tooling to do this automatically, but it is completely optional. ##### MessagingGroup A [`MessagingGroup`](#rellm-MessagingGroup) is the set of participants in a Message conversation. Every [`Message`](#rellm-Message) belongs to one; if a client wasn't a visible recipient (e.g. they were BCC'ed), the [`Message`](#rellm-Message) they receive omits it. ### Authentication Rellm uses a standard OAuth2 flow (over gRPC) for authentication, with rotating `access_token`s and `refresh_token`s (both [`ExpirableToken`s](#rellm-ExpirableToken)). Authenticated calls require an `access_token` in request metadata to be included / directly as the value of the `authorization` header (no `Bearer ` prefix). The `ExpirableToken` type allows clients to know ahead of time when their `access_token` and `refresh_token` are about to expire. First, before *any* authentication is done, you should [resolve your backend host](#http-based-client-host-negotiation-for-external-cdns-get-backend_host), and check its [`GetServiceVersion`](#grpc-api-GetServiceVersion) and [`GetServerConfiguration`](#grpc-api-GetServerConfiguration) RPCs. Check whether you have the `CREATE_ACCOUNT` and/or `LOGIN` [`AuthenticationFeature`](#rellm-AuthenticationFeature)s in your [`ServerConfiguration`](#rellm-ServerConfiguration). Next, use the [`CreateAccount`](#grpc-api-CreateAccount) or [`Login`](#grpc-api-Login) RPCs to fetch (and store) an initial `refresh_token` and `access_token`. Clients should use the `access_token` until it expires, then use the `refresh_token` to call the [`AccessToken`](#grpc-api-AccessToken) RPC for a new one. (The [`AccessToken`](#grpc-api-AccessToken) RPC may, at random, also return a new `refresh_token`. If so, it should immediately replace the old one in client storage.) #### Federated Authentication tl;dr: Lets you sign in to the `jon@bullcity.social` user on `jonline.io`, without ever entering your `bullcity.social` credentials on `jonline.io`. Elm-only feature (`frontends/elm-spa`) letting a user sign in to one Rellm server using an account they already have (or are willing to create) on a *different* Rellm server, without either backend ever seeing a plaintext token that isn't its own. It's pure browser-to-browser: two Elm SPA page routes ([`/auth/to/...`](#authtopublic_keyrequesting_host-sending-side) and [`/auth/from/...`](#authfromencrypted_account_auth_tokens-receiving-side)) exchange an encrypted pair of tokens via a full-page redirect; no gRPC/HTTP endpoint on either backend is involved beyond the [`Login`](#grpc-api-Login) RPC itself (plus [`GetCurrentUser`](#grpc-api-GetCurrentUser) on the receiving side, to hydrate everything else - see step 6). 1. Say a user is on `jonline.io`, adding a new account, and enters `bullcity.social` as the server. Since that isn't the current host, the Accounts panel offers a "Sign in via bullcity.social" button instead of (or alongside) a normal username/password form. 2. Clicking it does a full-page navigation to `bullcity.social`, carrying `jonline.io`'s ECDH public key (freshly generated in-browser and persisted for this purpose) and its own hostname in the URL: `/auth/to/{public_key}@jonline.io`. 3. `bullcity.social` shows its own sign-in form (or, if already signed in there, a badge to reuse that session), plus a "Sign back in here" checkbox, checked by default. 4. The user authenticates via the [`Login`](#grpc-api-Login) RPC. This always issues a *fresh* `refresh_token`/ `access_token` pair, reserved purely for transfer back to `jonline.io` - it's never used to sign the browser into `bullcity.social` itself. If "Sign back in here" is checked, a **second**, independent [`Login`](#grpc-api-Login) call also runs, so `bullcity.social` gets its own local session too, and the two servers never end up sharing a token pair. (Hence "1-2 refresh tokens.") 5. Only `bullcity.social`'s hostname and that fresh `refresh_token`/`access_token` pair are JSON-encoded and encrypted to `jonline.io`'s public key from step 2 (ephemeral ECDH + HKDF + AES-GCM - see below) - nothing else about the account travels in the payload. The browser is then redirected back to `jonline.io` at `/auth/from/{ciphertext}`. 6. `jonline.io` decrypts the payload with the private key it generated in step 2, calls [`GetCurrentUser`](#grpc-api-GetCurrentUser) against `bullcity.social` with the decrypted `access_token` to hydrate the rest of the account (user ID, username, avatar, permissions, etc. straight from `bullcity.social` itself rather than trusting a client-supplied copy of them), then adds it to its Accounts panel and navigates the user onward - no confirmation step. Either way, the one-time keypair generated in step 2 is discarded and a fresh one generated in its place, so it can't be reused for a second transfer. See the two [Web UI](#authtopublic_keyrequesting_host-and-authfromencrypted_account_auth_tokens-receiving-side) page routes below for the exact URL/crypto shape. ##### Developer option: signing in to an unsecure `localhost` For local development, a frontend running at plaintext `http://localhost[:port]` can be the *requesting* side of the flow above (e.g. signing in to your local dev server using an account from a production server). Since the encrypted tokens are then handed to a non-TLS origin, this is **off by default** and opt-in per *sending* server: an admin turns on `FederationInfo.unsecure_localhost_federated_auth_enabled` (checkbox on the Elm frontend's Server Information > Federation tab, saved on toggle). Without it: * [`/auth/to`](#authtopublic_keyrequesting_host-sending-side) refuses to log in or redirect when `{requesting_host}` is `localhost`, disabling its submit button and showing an error; * [`/auth/from`](#authfromencrypted_account_auth_tokens-receiving-side), when loaded over plain `http://`, will not call [`GetCurrentUser`](#grpc-api-GetCurrentUser) with tokens from a server that hasn't opted in. With it enabled, `/auth/to` redirects to `http://localhost/...` (rather than `https://`) and shows a warning that the sign-in is going to an unsecure localhost; only `localhost` is ever allowed over `http://`. This is purely a frontend-enforced guard -- the backend can't stop a modified client from moving its own tokens anywhere -- so only enable it on servers used for development. ### Federation Whereas other federated social networks (e.g. ActivityPub) have both client-server and server-server APIs, Rellm only has client-server APIs. While server-to-server communication is possible, nothing but some "nice to have" features require it, so it is not used. #### Federated Servers Rellm servers can recommend other servers to clients with the `federation_info` field (a [`FederationInfo` message](#rellm-FederationInfo)) in [`ServerConfiguration`](#rellm-ServerConfiguration). Clients can use this information to discover other servers, or users can add new servers manually. Note that, at least for web clients, this means everything is subject to CORS. In the future, Rellm will allow CORS to be configured in a "strict" mode, so someone else's Rellm server cannot be used to access your server's data unless you explicitly allow it. #### Federated Profiles Rellm users can federate with users on any other Rellm server. This works by two-way verification: For example, Jon has the user [`jonline.io/jon`](https://jonline.io/jon), [`oakcity.social/jon`](https://oakcity.social/jon), and [`bullcity.social/jon`](https://bullcity.social/jon) associated with one another. The UI will only show federated profiles if *both use profiles* have federated with one another. This mechanism also allows users to link multiple profiles on the same server together. For instance, [`bullcity.social/jon`](https://bullcity.social/jon) and [`bullcity.social/openmic`](https://bullcity.social/openmic) are linked together, but [`bullcity.social/openmic`](https://bullcity.social/openmic) isn't linked to [`jonline.io/jon`](https://jonline.io/jon) or [`oakcity.social/jon`](https://oakcity.social/jon). Federated profiles are managed via the `federated_profiles` field (a `repeated` [`FederatedAccount`](#rellm-FederatedAccount)) in the [`User`](#rellm-User) message. #### Federated Browsing Rellm's protocols and UI are designed to work together to present a seamless UX for content from many types of communities. Users can add/remove servers in a way that gives them control, transparency and trust. Meanwhile, server owners get extreme customization and useful integrations with social media platforms. #### Federated Messaging Rellm's Elm Messaging UI is generally a multi-server federated messenger. The main limitation is that it can only receive push notifications from one server. (This could be changed with VAPID key sharing, but is part of the VAPID protocol.) #### Federated Markets The Elm `/market` page can show more than one server's Market side by side: the browsed server's own (always first) plus any other connected, enabled server whose `ServerConfiguration.market_settings.enabled` is set (in Accounts panel order after that) - the public, non-secret "is this server's Market open" signal, unlike `stripe_config` itself, which stays Admin-only. Each section is fully independent: it lists that server's own [`MarketProduct`](#rellm-MarketProduct)s and shows product-management controls only if the current user is actually an Admin *on that server*. Unlike Federated Browsing/Profiles/Messaging above, buying is never seamless across servers - Market is the one place a user must always transact with the target site directly. A product tile for the browsed server links to its own in-app product page as usual, but a tile for any *other* server links straight to that server's own `https://{host}/market/product/{id}` (a real page navigation, not client-side routing), since [`MakeMarketPurchase`](#grpc-api-MakeMarketPurchase) starts a Stripe Checkout Session scoped to whichever server the buyer is actually authenticated against, and Stripe's own `success_url`/`cancel_url` redirect back to that same server when payment completes. ### HTTP Endpoints #### Internal HTTP server (27705) ##### `POST /email`: Stalwart Email Integration Delivery endpoint called by the [Stalwart](https://stalw.art) mail server (see [`deploys/email`](https://github.com/JonLatane/rellm/tree/main/deploys/email)'s [README](https://github.com/JonLatane/rellm/blob/main/deploys/email/README.md) for setup/architecture) once it accepts an inbound message addressed to one of this Rellm instance's onboarded domains, turning it into a [`Message`](#rellm-Message). It is **internal-only**: mounted solely on the unsecured 27705 server (never on 80/8000/443), has no authentication of its own, and trusts its caller completely - that trust boundary is expected to be enforced at the network layer (e.g. a `NetworkPolicy` restricting port 27705 to Stalwart's pod). * **Request**: the body is Stalwart's `data`-stage [MTA Hook](https://stalw.art/docs/mta/filter/mtahooks/) JSON payload (up to 50 MiB), not a raw MIME stream - only the fields below are read, the rest of Stalwart's payload (`context`, `envelope.from`, `message.serverHeaders`, `message.size`, ...) is ignored: ```json { "envelope": { "to": [{ "address": "someone@yourdomain.com" }] }, "message": { "headers": [["Subject", "Hello"], ["From", "sender@example.com"], ["To", "someone@yourdomain.com"]], "contents": "Hello, World!\r\n" } } ``` Recipients come from `envelope.to[].address` - deliberately the SMTP envelope, not the message's `To`/`Cc` headers, since that's the only place Bcc'd recipients show up at all. The message itself is reconstructed by concatenating `message.headers` (each an unfolded `[name, value]` pair) with `message.contents` across a blank line, which `mail_parser` then parses as the RFC822 message - Stalwart only splits at the top-level header/body boundary, so this still captures multipart bodies and attachments intact within `contents`. A body that isn't valid JSON in this shape, or that doesn't reconstruct into a parseable MIME message, returns `400 Bad Request`; an oversized body returns `413 Payload Too Large`. * **Recipient resolution**: each envelope address's local part (before the `@`) is looked up as a username on this server; addresses that don't match any user are silently skipped, since Stalwart is expected to have already confirmed deliverability before calling this endpoint. If none match, the whole message is dropped and the endpoint returns `404 Not Found`. * **Storage**: matched recipients become a [`Message`](#rellm-Message) addressed to a [`MessagingGroup`](#rellm-MessagingGroup) keyed on the `To`/`Cc` recipients only - Bcc'd recipients are excluded from the group (so they stay invisible to everyone else on the thread) and instead recorded individually as `Bcc` rows on the [`Message`](#rellm-Message). The [`Message`](#rellm-Message) has no `from_user_id`, since inbound email never has a local sender; its parsed `from`/`to`/`cc` headers are stored alongside it, and the raw `.eml` is uploaded to the same object storage used for [`Media`](#rellm-Media). Duplicate deliveries of the same `Message-ID` (Stalwart retries on transient failure) reuse the existing [`Message`](#rellm-Message) row rather than storing/uploading a duplicate. * **Response**: `200 OK` with a body of `{"action": "accept"}` on success - Stalwart's MTA Hook protocol parses the response *body*, not just the status code, so this has to be the exact shape it expects (see <https://stalw.art/docs/mta/filter/mtahooks/>) or Stalwart treats the call as a hook failure regardless of status; combined with the `MtaHook`'s `tempFailOnError: true`, that surfaces to the sending client as a `451` temp-fail rather than anything indicating the real cause. #### External HTTP servers (80, 8000, 443) Note that, if the TLS server on port 443 starts up successfully, the server on port 80 will simply redirect to HTTPS. The server on port 8000 will always serve up unsecured HTTP. It is up to server admins to block this port if they find that necessary. ##### `GET /backend_host`: HTTP-based client host negotiation (for external CDNs) When first negotiating the gRPC connection to a host, say, `jonline.io`, before attempting to connect to `jonline.io` via gRPC on 27707/443, the client is expected to first attempt to `GET jonline.io/backend_host` over HTTP (port 80) or HTTPS (port 443) (depending upon whether the gRPC server is expected to have TLS). If the `backend_host` string resource is a valid domain, say, `jonline.io.itsj.online`, the client is expected to connect to `jonline.io.itsj.online` on port 27707/443 instead. To users, the server should still *generally* appear to be `jonline.io`. The client can trust `jonline.io/backend_host` to always point to the correct backend host for `jonline.io`. This negotiation enables support for external CDNs as frontends. See https://jonline.io/about?section=cdn for more information about external CDN setup. Developers may wish to review the [React/Tamagui](https://github.com/JonLatane/rellm/blob/main/frontends/tamagui/packages/app/store/clients.ts#L116) and [Flutter](https://github.com/JonLatane/rellm/blob/main/frontends/flutter/lib/models/rellm_clients.dart#L26) client implementations of this negotiation. ##### `GET /robots.txt`: Robots Generated on the fly (not a static file) from the request's `Host` header, publicly cacheable for 1 hour. Always allows all crawling (`User-agent: * / Allow: /`) and points crawlers at `https://{host}/sitemap.xml`. ##### `GET /sitemap.xml`: Sitemap Generated on the fly (not a static file) from the request's `Host` header, publicly cacheable for 1 hour. Lists a fixed set of top-level, server-wide pages - `/`, `/posts`, `/events`, `/people`, `/about`, `/about_rellm`, `/flutter`, `/tamagui`, `/elm` - plus any `CustomNavigationTabSet.tabs` paths configured on the server (excluding the reserved `posts`/`events`/`people`/`about` paths, which are always included above), each qualified with the request's `Host`. It also enumerates individual pages: every [`Post`](#rellm-Post) from an unauthenticated [`GetPosts`](#grpc-api-GetPosts) (the same "first page" an anonymous visitor sees) as `/post/{id}`, and every [`Occasion`](#rellm-Occasion) from an unauthenticated [`GetEvents`](#grpc-api-GetEvents) starting `EventSettings.calendar_lookback_days` (or 14, if unset) ago as `/event/{occasion_id}`. It does not (yet) enumerate individual [`User`](#rellm-User) pages. ##### `GET /favicon.ico`: ICO Favicon Serves the server's configured logo (`ServerConfiguration.server_info.logo.square_media_id`, a [`Media`](#rellm-Media) reference) as an `.ico`, publicly cacheable for 12 hours (`must-revalidate`), converting on the fly if the stored rendition is a `.png`. If no logo is configured, falls back to the bundled Tamagui frontend's default favicon instead. Whichever converted rendition of the logo is served, it's picked in size preference order Medium, then Small, then Large, then the original upload if none of those conversions exist (favicons are small, so there's no reason to prefer a bigger one). ##### `GET /favicon.png`: PNG Favicon As `GET /favicon.ico` above, but serves (and if necessary converts to) `.png` instead. ##### `POST /media`: Upload Media See the [Media](#rellm-Media) section for the [`Media`](#rellm-Media) type itself; this is how its bytes actually get in (an `OPTIONS /media` variant also exists, solely to satisfy CORS preflight requests). *Authenticated* (via `Authorization` header or a `rellm_access_token` cookie). Requires `Content-Type` and `Filename` headers; the body is streamed directly to the object store, capped at 250 MiB - note that a larger upload is silently truncated to that cap rather than rejected, since nothing checks for completeness the way `POST /email` does -- at a path namespaced by uploader and request host (`user/{user_id}@{host}-{username}/{uuid}-{filename}`). A [`Media`](#rellm-Media) row is created immediately at `GLOBAL_PUBLIC` visibility (video content types also get a default `video_preview_time_ms`) and its ID returned as plain text - there's no separate "confirm" step, and no image/video conversion happens synchronously on this request (see the background media-conversion job). ##### `GET /media/{id}?size={original|small|medium|large}`: Download Media (An `OPTIONS /media/{id}` variant also exists, solely to satisfy CORS preflight requests.) Publicly downloadable - **moderation/visibility/permission checks on read are not yet enforced** (a `TODO` in `media_file`'s implementation), so a [`Media`](#rellm-Media) ID is currently a bearer capability. `size` (default `medium`) selects a converted rendition, falling back to the original upload if that conversion doesn't exist. The first request for a given rendition lazily downloads it from the object store into a local on-disk cache; subsequent requests are served from that cache. Cacheable for 12 hours (`must-revalidate`). ##### `GET /calendar.ics`: Server Calendar Rellm events support iCalendar/RFC5545; only public events are included. "Subscribe" to a Rellm server at, for instance, `https://jonline.io/calendar.ics` to get a calendar of all public events on the server. In the Tamagui/React frontend, links to these endpoints are provided in the Upcoming Events section of the home page, the Events page, and the user profile pages for all users with events in the last 3 months (or in the future). ##### `GET /calendar.ics?user_id={id}`: User Calendar "Subscribe" to a user's calendar at, for instance, `https://jonline.io/calendar.ics?user_id=CruFm` to get a calendar of all public events for that user. ##### `GET /calendar.ics?post_id={id}`: Event or Occasion Calendar Serves a calendar for a single [`Event`](#rellm-Event) -- e.g. for an "Add to Calendar" download. If `post_id` is the Event's own Post ID, every one of its [`Occasion`](#rellm-Occasion)s is included; if it is an Occasion's Post ID, only that Occasion is. `post_id` takes precedence over `user_id`, and the calendar is named after the Event's title. Each VEVENT has a stable `UID` of `{occasion_post_id}@{frontend_domain}`, so re-importing updates rather than duplicates. In the Elm frontend, this powers the event/occasion "Add to Calendar" export. ##### `GET /calendar.ics?anonymous_auth_token={token}`: Anonymous RSVP Calendar Optional on any of the `/calendar.ics` forms above. Passed straight through to `GetEventsRequest.anonymous_attendee_auth_token`, so it accepts the same forms: a plain `<token>`, or several as `<occasionId>-<token>--<occasionId>-<token>...` (as in the web frontends' `?anonymousAuthToken=` parameter). It reveals the real location of Occasions whose location is hidden until the viewer's RSVP is approved (`EventInfo.hide_location_until_rsvp_approved`), and adds a "manage your RSVP" link (`/event/{occasion_id}?anonymousAuthToken={token}`) to the description of each Occasion the token holds an RSVP for. Treat such URLs as secrets, since the token alone grants control of that RSVP. ##### `GET /rss.xml` / `GET /atom.xml`: Server Posts Feed The reverse direction of a `SyncSource`'s own RSS/Atom subscription (see the SyncSources section above): serves Rellm's own [`Post`](#rellm-Post)s back out as a feed, only public Posts included. "Subscribe" to a Rellm server at, for instance, `https://jonline.io/rss.xml` (or `/atom.xml`) to get a feed of all public posts on the server, in whichever of the two formats a given feed reader prefers - both endpoints serve the same underlying Posts. In the Elm frontend, links to these endpoints are provided next to the Posts page's own search controls, and on the home page and user profile pages' embedded posts lists. ##### `GET /rss.xml?user_id={id}` / `GET /atom.xml?user_id={id}`: User Posts Feed "Subscribe" to a user's posts at, for instance, `https://jonline.io/rss.xml?user_id=CruFm` (or `/atom.xml?user_id=CruFm`) to get a feed of all public posts for that user. ### Web UI paths Rellm serves three web frontends from the same backend: Tamagui (React/Next.js), Elm, and Flutter. Tamagui and Elm share one page structure (below) and are always *both* reachable, explicitly, at `/tamagui/*` and `/elm/*` respectively; unprefixed requests (`/`, `/posts`, `/post/{postId}`, etc.) render whichever of the two the server's `ServerConfiguration.server_info.web_user_interface` selects (`ELM_SPA` picks Elm; every other setting, including no preference at all, picks Tamagui). Elm is a genuine single-page app - every Elm-served path, prefixed or not, resolves to the same `index.html`, with in-app (client-side) routing taking over from there - whereas Tamagui's Next.js build is statically exported one HTML file per route, so the server picks between actual distinct files below, each enriched with server-rendered, per-route social-preview (`<title>`/`og:*`) tags before being served. **Flutter does not participate in any of this.** It has no page structure of its own to speak of: no per-route pages, no server-rendered social-preview metadata, and no unprefixed presence at all - a server configured to prefer it doesn't route "/" through the Tamagui/Elm machinery below and then render Flutter, it instead serves Flutter's own `index.html` directly, bypassing that machinery entirely. Flutter is otherwise reached only at the literal `/flutter` and `/flutter/*` paths, which serve its compiled static assets; from there, all further in-app navigation is handled entirely client-side by Flutter's own router and is invisible to the server. The shared Tamagui/Elm page structure, grouped the way the [Elm app's `Pages` directory](https://github.com/JonLatane/rellm/tree/main/frontends/elm-spa/src/Pages) is (`{name}` denotes a dynamic path segment; `[@{host}]` marks where a [federated](#federated-profiles) `{username}@{host}`-style suffix is accepted for that segment): #### `/`: Home The community's latest activity. #### `/posts`: Posts The Posts listing. ##### `/post/{postId}[@{host}]`: Post An individual [`Post`](#rellm-Post) - including [`Event`](#rellm-Event)/[`Occasion`](#rellm-Occasion) posts and replies, which are [`Post`](#rellm-Post)s themselves (see [Post](#post) above). #### `/events`: Events The Events listing. #### `/[-._~:/?[]@!$&'()*+,;%=]{postId}`: Short Post/Event URLs A [`Post`](#rellm-Post) or [`Event`](#rellm-Event)/[`Occasion`](#rellm-Occasion), reached at its own `post.id` prefixed with any single character a username/custom tab path could never legally start with (see [`validate_username`](https://github.com/JonLatane/rellm/blob/main/backend/src/rpcs/validations/validate_fields.rs)'s own reserved-lead-character check) - e.g. `jonline.io/:4rAfoSKAuJo` or `ato.band/~4rAfoSKAuJo`. This is purely a shorter, friendlier alias for `/post/{postId}[@{host}]` or `/event/{postId}[@{host}]` (whichever the id turns out to belong to) - it renders exactly that same content in place, without redirecting the address bar away from the short URL. `#` is deliberately excluded from the reserved set: URL fragments never reach the server, so they can't be used for this. ##### `/event/{postId}[@{host}]`: Event An individual [`Event`](#rellm-Event), looked up by its own `post.id` or any of its [`Occasion`](#rellm-Occasion)s' `post.id`s. ##### `/event_ai`: AI Event Importer Tamagui-only, for now - an AI-assisted bulk [`Event`](#rellm-Event) importer. Elm doesn't have this page yet. #### `/people`: People The People listing. ##### `/people/follow_requests`: Follow Requests The current user's pending [`Follow`](#rellm-Follow) requests. ##### `/user/{userId}`: Profile A [`User`](#rellm-User) profile looked up by (stable) user ID. #### `/{custom_tab_or_username}`: User pages by username, or a custom tab The same [`User`](#rellm-User) profile (and its Posts/Friends/Followers/Following sub-pages) as `/user/{userId}` above, but looked up by the current `username` instead - lighter-weight to link to, but less stable than `/user/{userId}` since a username can change. This single path segment is also the server's last-resort catch-all, resolved in order: first any actual matching build asset or other explicit route above (e.g. `/posts`, `/user/{userId}`) wins outright; then, if none matched, an admin-configured custom tab path (see [`CustomNavigationTab`](#rellm-CustomNavigationTab).path) - e.g. a band mounting their Events listing at `/gigs` - wins over a same-named user; only then, last, is it looked up as a plain username. A small set of reserved names can never be reached this way, only via `/user/{userId}`. ##### `/{username}/posts`: Posts ##### `/{username}/friends`: Friends ##### `/{username}/followers`: Followers ##### `/{username}/following`: Following #### `/g/{shortname}`: Groups A [`Group`](#rellm-Group)'s pages. Tamagui-only for now - the Elm frontend doesn't have Group pages yet. ##### `/g/{shortname}`: Home ##### `/g/{shortname}/posts`: Posts ##### `/g/{shortname}/p/{postId}[@{host}]`: Post An individual [`Post`](#rellm-Post) cross-posted into the group. ##### `/g/{shortname}/events`: Events ##### `/g/{shortname}/e/{occasionId}[@{host}]`: Event ##### `/g/{shortname}/members`: Members ##### `/g/{shortname}/m/{username}`: Member An individual [`Member`](#rellm-Member)'s details. #### `/server/{serverIdentifier}`: Server Information about a (possibly federated) Rellm server. #### `/about`, `/about_rellm`: About This server's own About page, and a general "what is Rellm" page. #### `/auth/to/{public_key}@{requesting_host}` and `/auth/from/{encrypted_account_auth_tokens}`: Federated Sign-In **Elm-only** - unlike everything else in this section, these two paths have no Tamagui equivalent. They're Elm SPA pages (served like any other SPA route - under the `/elm` base path when the Elm frontend isn't the one mounted at `/`) rather than backend/gRPC handlers, driving the [Federated Authentication](#federated-authentication) flow entirely in-browser via a pair of full-page redirects carrying an encrypted payload. ##### `/auth/to/{public_key}@{requesting_host}`: sending side `Pages.Auth.To.Key_`. Reached only via the cross-origin redirect from step 2 above (built by the *requesting* origin's Accounts panel), never linked to directly. * **Path params**: `{public_key}` is the requesting origin's ECDH (P-256) public key, raw-exported and base64url-encoded; `{requesting_host}` is that origin's own hostname. The two are joined with a literal `@` (chosen because `@` never appears in the base64url/dot-joined ciphertext the [`/auth/from`](#authfromencrypted_account_auth_tokens-receiving-side) page below expects, so the split is unambiguous). * **Query params**: `start_path` - the app-relative path the user was on when they clicked "Sign in via ...", so they can be dropped back there after the round trip. Percent-encoded; passed through unchanged to the eventual [`/auth/from`](#authfromencrypted_account_auth_tokens-receiving-side) redirect. * **Behavior**: shows a sign-in form for *this* server (or a "currently signed in as ..." badge, if already authenticated here), plus a "Sign back in here"/"Also sign in here" checkbox (checked by default). Submitting calls the [`Login`](#grpc-api-Login) RPC (always a fresh login, never reusing stored tokens) to mint the transfer tokens; if the checkbox is checked, a second independent [`Login`](#grpc-api-Login) call also signs the browser into this server locally. `{requesting_host}`'s hostname plus that fresh `refresh_token`/`access_token` pair are then AES-GCM-encrypted to `{public_key}` (fresh ephemeral ECDH keypair per encryption, shared secret via ECDH + HKDF-SHA256, output `ephemeral_public_key.iv.ciphertext`, each part base64url) and the browser is redirected to `https://{requesting_host}/auth/from/{ciphertext}?start_path={start_path}`. ##### `/auth/from/{encrypted_account_auth_tokens}`: receiving side `Pages.Auth.From.EncryptedAccountAuthTokens_`, closing the loop from [`/auth/to`](#authtopublic_keyrequesting_host-sending-side) above. Reached only via that redirect. * **Path params**: `{encrypted_account_auth_tokens}` is the `ephemeral_public_key.iv.ciphertext` blob produced by [`/auth/to`](#authtopublic_keyrequesting_host-sending-side). * **Query params**: `start_path`, passed through unchanged from [`/auth/to`](#authtopublic_keyrequesting_host-sending-side); defaults to `/` if missing. * **Behavior**: decrypts `{encrypted_account_auth_tokens}` using the private key this origin generated when it built the [`/auth/to`](#authtopublic_keyrequesting_host-sending-side) link (same ECDH + HKDF-SHA256 + AES-GCM derivation, in reverse), yielding `bullcity.social`'s hostname and its `refresh_token`/`access_token`. Calls [`GetCurrentUser`](#grpc-api-GetCurrentUser) against that server with the decrypted `access_token` to hydrate the rest of the account, then adds it straight to the local Accounts panel and navigates to `start_path` - no confirmation step (decryption succeeding is itself the authenticity check: the ciphertext is AEAD-encrypted to this origin's own one-time private key, so a forged or replayed payload just fails to decrypt rather than producing a wrong-but-valid account). If either step fails (bad decrypt, or the `GetCurrentUser` call itself), an error is shown instead. Either way, once the flow completes (added, or failed), the one-time private key is discarded and a fresh keypair generated, so it's single-use per completed/failed transfer. ### gRPC API

message AIGrantPurchaseDetails

market.proto:345

`MarketPurchase.details`' `PURCHASE_TYPE_AI_GRANTS` variant -- copied verbatim from the originating `MarketProduct.details` at the moment this purchase was fulfilled. Field-for-field identical to `AIGrantSubscriptionDetails` -- see `MediaStoragePurchaseDetails`'s own doc for why it's still a separate message.

Used in: MarketPurchase

message AIGrantSubscriptionDetails

market.proto:544

`MarketProduct.details`/`MarketSubscription.details`' `PURCHASE_TYPE_AI_GRANTS` variant -- what an AI token product actually grants. Field-for-field identical to `AIGrantPurchaseDetails` -- see that message's own doc for why it's still a distinct type.

Used in: MarketProduct, MarketSubscription

message AIModel

ai_providers.proto:16

One specific model a user may call right now, and how - via an [`AIProvider`](#rellm-AIProvider) they own outright (`grant` unset), or via an [`AIProviderGrant`](#rellm-AIProviderGrant) someone else granted them (`grant` set). Only ever defined relative to a user - see [`User.ai_models`](#rellm-User)/[`GetAIProvidersResponse.ai_models`](#rellm-GetAIProvidersResponse). One `AIModel` exists per (provider, model) pair: an owner gets one row per model their provider supports (see the server's own model catalog per provider type); a grantee gets one row per model their grant actually covers - expanded from `AIProviderGrant.model_names`, or every model the provider supports if that list is empty.

Used in: GenerateMediaRequest, GetAIProvidersResponse, User

enum AIModelCapability

ai_providers.proto:42

What an [`AIModel`](#rellm-AIModel) can actually do - drives feature gating (e.g. [`GenerateMedia`](#grpc-api-GenerateMedia)'s "Generate Media…" buttons/panel only offer models carrying `AI_MODEL_CAPABILITY_IMAGE_EDITING`/`AI_MODEL_CAPABILITY_IMAGE_GENERATION`) without the gated feature needing its own hardcoded list of model names to check against. A model may carry more than one - e.g. an image-editing model can also usually do plain text-to-image generation.

Used in: AIModel

message AIProvider

ai_providers.proto:122

An AIProvider is a user-owned connection to an external AI model API (e.g. a Gemini API key), which its owner can grant other users of this server metered, budgeted access to. Mirrors [`SyncDestination`](#rellm-SyncDestination)/[`SyncSource`](#rellm-SyncSource) (also user-owned integrations with an [`Author`](#rellm-Author) `owner` and a `oneof` naming which external system is configured), but where those push/pull content, an AIProvider is metered *access* to a third-party LLM API - shared out to other users via [`AIProviderGrant`](#rellm-AIProviderGrant)s rather than posted-to/subscribed-from. Providers are managed via [`GetAIProviders`](#grpc-api-GetAIProviders), [`CreateAIProvider`](#grpc-api-CreateAIProvider) (requires `CREATE_AI_PROVIDERS`, or Admin), [`UpdateAIProvider`](#grpc-api-UpdateAIProvider) (owner, or Admin for any user's), and [`DeleteAIProvider`](#grpc-api-DeleteAIProvider) (owner, or Admin) - the same self-or-Admin shape as [`SyncDestination`](#rellm-SyncDestination)'s RPCs. Access to a provider is granted/revoked to other users via [`GrantAIProvider`](#grpc-api-GrantAIProvider)/[`RevokeAIProvider`](#grpc-api-RevokeAIProvider) which, unlike every other RPC pair here, are **owner-only with no Admin override**: an Admin can manage the provider record itself (rename it, rotate its key, delete it), but handing out access to *someone else's* API budget is a call only its owner should be able to make. [`GeminiCredentials`](#rellm-GeminiCredentials)/[`OpenAICredentials`](#rellm-OpenAICredentials)/ [`DigitalOceanCredentials`](#rellm-DigitalOceanCredentials) all have a working connection flow (Gemini's Interactions API, OpenAI's Images API, DigitalOcean's Serverless Inference API - the last of which is also OpenAI-Images-API-shaped, just a different base URL/key and generation-only, no editing endpoint); [`AnthropicCredentials`](#rellm-AnthropicCredentials) is defined for forward compatibility but is not yet accepted by [`CreateAIProvider`](#grpc-api-CreateAIProvider) (Anthropic doesn't offer image generation).

Used as request type in: Rellm.CreateAIProvider, Rellm.UpdateAIProvider

Used as response type in: Rellm.CreateAIProvider, Rellm.UpdateAIProvider

Used as field type in: AIModel, DeleteAIProviderRequest, GetAIProvidersResponse

message AIProviderGrant

ai_providers.proto:174

A grant of metered access to someone else's [`AIProvider`](#rellm-AIProvider), created/reset via [`GrantAIProvider`](#grpc-api-GrantAIProvider) and removed via [`RevokeAIProvider`](#grpc-api-RevokeAIProvider). Upserted on the unique `(ai_provider_id, ai_model_grantee)` pair - calling [`GrantAIProvider`](#grpc-api-GrantAIProvider) again for a user who already has a grant *resets* `tokens_remaining` to the newly-requested amount, it does not add to it.

Used as response type in: Rellm.GrantAIProvider

Used as field type in: AIModel, AIProvider

message AnonymousAttendee

events.proto:347

An anonymous internet user who has RSVP'd to an [`Occasion`](#rellm-Occasion). (TODO:) The visibility on `AnonymousAttendee` [`ContactMethod`](#rellm-ContactMethod)s should support the `LIMITED` visibility, which will make them visible to the event creator.

Used in: Rsvp

message AnthropicCredentials

ai_providers.proto:292

Credentials for an [Anthropic API](https://docs.anthropic.com) connection. *Not yet creatable* - defined for forward compatibility only.

Used in: AIProvider

enum AuthenticationFeature

server_configuration.proto:349

Authentication features that can be enabled/disabled by the server admin.

Used in: ServerConfiguration

message Author

media.proto:303

Post/authorship-centric version of User. UI can cross-reference user details from its own cache (for things like admin/bot icons). Lives in `media.proto` (rather than `users.proto`, where it used to live, or its own `authors.proto`, split out from `users.proto` for a time) because `Author.avatar` needs `MediaReference` and `Media`/`MediaReference` need `Author` (see this field's own doc) -- mutually recursive types belong in the same file, since `protoc` rejects circular *file* imports even though the recursive *types* themselves are perfectly valid. `users.proto` (`User.sync_destinations`) and `sync.proto` (`SyncDestination.owner`, `SyncSource.owner`) both depend on this without depending on each other, via their own `import "media.proto"` (both already needed it anyway, for `User.avatar`/`Media`-shaped fields).

Used in: AIProvider, AIProviderGrant, GroupPost, License, MarketPurchase, MarketSubscription, Media, MediaReference, Message, MessagingGroup, Post, SyncDestination, SyncSource

message BirdConfig

server_configuration.proto:906

Bird (https://bird.com, formerly MessageBird) Config -- an alternative SMS verification provider to [`TwilioConfig`](#rellm-TwilioConfig), with a simpler single-API-key auth model. See `docs/contact_integrations.md` for full setup steps.

Used in: ServerConfiguration

message BlueskyAccount

sync.proto:138

A Bluesky (AT Protocol) account connected as a [`SyncDestination`](#rellm-SyncDestination) via an "App Password" (generated at Settings > App Passwords - not the account's main password), rather than an OAuth popup. Media limitation: only attached *images* on a synced Post/Occasion are posted (up to 4, downloaded and re-uploaded as Bluesky blobs) - video is silently dropped entirely. Bluesky video embeds need a separate, more complex upload-and-processing flow not yet built.

Used in: SyncDestination

message BlueskyServer

federation.proto:129

A Bluesky/AT Protocol AppView clients may read public posts from anonymously. See `FederationInfo.bluesky_servers`. AT Protocol splits "a server" into several roles: a Personal Data Server (PDS) stores an account's data and handles its login, while an AppView serves the read side (feeds, threads, profiles). Every AppView indexes the same global network, so an anonymous "Bluesky server" in Rellm's UI is really an AppView - the public one run by Bluesky PBC is `public.api.bsky.app`, which allows unauthenticated reads (with CORS) of public data. A connected *account* is separate: it logs in at its own PDS (not necessarily `bsky.social`), which proxies reads to an AppView on the account's behalf.

Used in: FederationInfo

enum CalendarDisplayMode

server_configuration.proto:549

The Events Calendar's default UI granularity.

Used in: CustomHomePage, EventSettings

message ClusterConductorState

server_configuration.proto:228

The conductor's live view of currently-held locks - one `ClusterResourceLock` per distinct holder (a given namespace can appear at most once here - `LockClusterResources` never grants a `ClusterResource` it's already granted that same `namespace_id`, and folds any additional resources into that namespace's existing entry rather than creating a second one - see that RPC's own doc). With a `limits` entry above `1` (see `ClusterResourceLimit`'s own doc), more than one distinct namespace can hold the *same* `ClusterResource` at once, so there can be more entries here than there are `ClusterResource` values. See `ClusterResources.conductor_state`.

Used in: ClusterResources

enum ClusterResource

server_configuration.proto:305

A resource `ClusterResources.conductor_host` can hand out an exclusive, cluster-wide lock on via [`LockClusterResources`](#grpc-api-LockClusterResources)/ [`FreeClusterResources`](#grpc-api-FreeClusterResources).

Used in: ClusterResourceLimit, ClusterResourceLock, FreeClusterResourcesRequest, LockClusterResourcesRequest

message ClusterResourceLimit

server_configuration.proto:262

One `ClusterResource`'s configured concurrency limit - a single `resource`/`limit` pairing per message (both fields are singleton lists in practice; see `ClusterConductorState.limits`'s own doc for why a `ClusterResource` missing from every `ClusterResourceLimit` here defaults to `1` rather than `0`).

Used in: ClusterConductorState

message ClusterResourceLock

server_configuration.proto:249

One namespace's currently-held lock on one or more `ClusterResource`s, and when it acquired them - shown in `ClusterTab`'s Elm UI so an admin can tell a genuinely stuck lock (acquired long ago, its holder's job surely long dead) from one just in normal, brief use, and reach for `free_all_cluster_resources` (a `bin/` admin tool - see its own doc) accordingly.

Used in: ClusterConductorState

message ClusterResources

server_configuration.proto:173

Coordinates a small piece of shared, cluster-wide state across multiple independent Rellm server instances that are otherwise fully isolated from each other (separate databases, separate [`FederationInfo`](#rellm-FederationInfo), etc.) but happen to run on shared underlying infrastructure (e.g. several Kubernetes namespaces sharing one small node pool). Currently used for exactly one thing: making sure only one instance has a headless Chrome/Brave browser open at any given moment (for generating link preview images), since launching several at once can exhaust a shared node's CPU/memory. One participating instance is designated the "conductor" (see `conductor_host`) and brokers locks via [`LockClusterResources`](#grpc-api-LockClusterResources)/ [`FreeClusterResources`](#grpc-api-FreeClusterResources); every instance in the cluster -- including the conductor itself - sets its own `ClusterResources` pointing at whichever host that is. See `ServerConfiguration.cluster_resources`'s own doc for who can see/edit this.

Used in: ServerConfiguration

message ContactConsentChange

users.proto:241

A single entry in a [`ContactMethod.consent_history`](#rellm-ContactMethod), recording that its `consent_state` became `state` as of `changed_at`. Our ultimate consent state is always the `state` of the most recent (last) entry in `consent_history` -- [`ContactMethod.consent_state`](#rellm-ContactMethod) is just a denormalized copy of it, kept for convenient access without walking the history.

Used in: ContactMethod

enum ContactConsentState

users.proto:228

Whether a user has consented to being contacted (e.g. via SMS/email sent by external services) through a given [`ContactMethod`](#rellm-ContactMethod). See [`ContactMethod.consent_state`](#rellm-ContactMethod).

Used in: ContactConsentChange, ContactMethod

message ContactMethod

users.proto:169

A contact method for a user (`tel:` or `mailto:`). SMS verification via [`StartContactMethodVerification`](#grpc-api-StartContactMethodVerification)/ [`VerifyContactMethod`](#grpc-api-VerifyContactMethod), backed by whichever of [`TwilioConfig`](#rellm-TwilioConfig)/[`BirdConfig`](#rellm-BirdConfig)/ [`TelnyxConfig`](#rellm-TelnyxConfig) the server has enabled -- see `supported_by_server` below, and [`ContactProtocol`](#rellm-ContactProtocol) for the corresponding server-wide toggle. `mailto:` has no verification provider yet.

Used as request type in: Rellm.StartContactMethodVerification

Used as response type in: Rellm.StartContactMethodVerification, Rellm.VerifyContactMethod

Used as field type in: AnonymousAttendee, CreateAccountRequest, User

message ContactMethodVerification

users.proto:253

Encapsulates verification of a [`ContactMethod`](#rellm-ContactMethod). Verification cannot begin until contact consent (`ContactMethod.consent_state`) is granted -- see [`StartContactMethodVerification`](#grpc-api-StartContactMethodVerification).

Used in: ContactMethod

enum ContactProtocol

server_configuration.proto:804

The two contact schemes [`ContactMethod.value`](#rellm-ContactMethod) (`users.proto`) may take -- see [`ServerConfiguration.supported_contact_protocols`](#rellm-ServerConfiguration) for the server-wide setting keyed off this enum, and `docs/contact_integrations.md` for the full picture.

Used in: ServerConfiguration

enum ContactVerificationAPI

server_configuration.proto:817

The SMS providers [`ServerConfiguration.preferred_verification_apis`](#rellm-ServerConfiguration)/ [`available_verification_apis`](#rellm-ServerConfiguration) order between -- [`TwilioConfig`](#rellm-TwilioConfig), [`BirdConfig`](#rellm-BirdConfig), and [`TelnyxConfig`](#rellm-TelnyxConfig). See `contact_verification.rs`'s own module doc for the preference-then-fallback logic these values drive.

Used in: ServerConfiguration

message CreateThirdPartyRefreshTokenRequest

authentication.proto:54

Request to create a new third-party refresh token. Unlike [`LoginRequest`](#rellm-LoginRequest) or [`CreateAccountRequest`](#rellm-CreateAccountRequest), the user must be logged in to create a third-party refresh token. Generally, this is used to create a refresh token for another Rellm instance, e.g., accessing `bullcity.social/jon`'s data from `jonline.io`. On the web side, this is implemented as follows: 1. When the `bullcity.social` user wants to login on `jonline.io`, `bullcity.social` will redirect the user to `jonline.io/third_party_auth?to=bullcity.social`. 2. `jonline.io` will force the user to login if needed on this page. 3. `jonline.io` will prompt/warn the user, and then call this RPC to create a refresh + access token for `bullcity.social`. 4. `jonline.io` will redirect the user back to `bullcity.social/third_party_auth?from=jonline.io&token=<Base64RefreshTokenResponse>` with the refresh token POSTed in form data. * (`<Base64RefreshTokenResponse>` is a base64-encoded [`RefreshTokenResponse`](#rellm-RefreshTokenResponse) message.) 6. `bullcity.social` will ensure it can [`GetCurrentUser`](#grpc-api-GetCurrentUser) on `jonline.io` with its new auth token. 5. `bullcity.social` will replace the current location with `bullcity.social/third_party_auth?from=jonline.io`. 7. `bullcity.social` will use the access token to make requests to `jonline.io` (the same as with `bullcity.social`). Note that refresh tokens

message CustomCSSConfiguration

server_configuration.proto:638

Custom CSS for a Rellm server's Elm SPA, plus the appearance settings that go with it. Stored alongside the [`ServerConfiguration`](#rellm-ServerConfiguration) (so it is versioned the same way, with each [`ConfigureServer`](#grpc-api-ConfigureServer) or [`ConfigureCustomCSS`](#grpc-api-ConfigureCustomCSS) creating a new configuration version) and served to browsers as a stylesheet at `/custom_css.css` (`/elm/custom_css.css` when the Elm SPA is served under `/elm`). The stylesheet always starts with a `:root` block defining CSS variables for custom CSS to use: `--primary-color` and `--nav-color` (the server's configured `ServerColors.primary` and `ServerColors.navigation`, as `#rrggbb`), and `--custom-media-1`, `--custom-media-2`, ... (see `media_ids`). The potentially large `custom_css` text is only ever returned by [`GetCustomCSS`](#grpc-api-GetCustomCSS) and [`ConfigureCustomCSS`](#grpc-api-ConfigureCustomCSS): [`GetServerConfiguration`](#grpc-api-GetServerConfiguration) includes this message as `ServerConfiguration.custom_css_configuration` but with `custom_css` unset, and never reads it from the database. [`ConfigureServer`](#grpc-api-ConfigureServer) ignores any `custom_css_configuration` it is sent, always leaving the stored one unchanged.

Used as request type in: Rellm.ConfigureCustomCSS

Used as response type in: Rellm.ConfigureCustomCSS, Rellm.GetCustomCSS

Used as field type in: ServerConfiguration

message CustomHomePage

server_configuration.proto:715

Overrides the app's default `/` page (the combined Events+Posts feed). Unlike a regular `CustomNavigationTab`, this has no `path` (it's always `/`) and no `icon`/`title` (the server's own name/logo are always shown for the Home tab in the nav, regardless of what it links to).

Used in: CustomNavigationTabSet

message CustomNavigationTab

server_configuration.proto:748

Either one of the app's predefined tabs, a Post, or a user profile - reachable at `path`.

Used in: CustomNavigationTabSet

message CustomNavigationTabSet

server_configuration.proto:657

If set, overrides the default tab set for the Elm navigation on a Rellm instance.

Used in: ServerConfiguration

message DigitalOceanCredentials

ai_providers.proto:283

Credentials for a [DigitalOcean Gradient AI Platform](https://docs.digitalocean.com/products/gradient-ai-platform/) / Serverless Inference connection, accepted by [`CreateAIProvider`](#grpc-api-CreateAIProvider)/[`UpdateAIProvider`](#grpc-api-UpdateAIProvider). Used for image *generation only* (no editing - see `AIProvider.provider`'s own doc on this variant) via its [Serverless Inference API](https://docs.digitalocean.com/products/gradient-ai-platform/reference/api/serverless-inference/) `/v1/images/generations` endpoint, OpenAI-Images-API-shaped and re-hosting GPT Image and Stable Diffusion models -- see [`GenerateMedia`](#grpc-api-GenerateMedia).

Used in: AIProvider

message Event

events.proto:130

An `Event` is a top-level type used to organize calendar events, RSVPs, and messaging/posting about the `Event`. Actual time data lies in its `Occasions`. (Eventually, Rellm Events should also support ticketing.)

Used as request type in: Rellm.CreateEvent, Rellm.CreateNewOccasions, Rellm.DeleteEvent, Rellm.DeleteRemovedOccasions, Rellm.UpdateEvent, Rellm.UpdateEventDetails, Rellm.UpdateOccasions

Used as response type in: Rellm.CreateEvent, Rellm.CreateNewOccasions, Rellm.DeleteEvent, Rellm.DeleteRemovedOccasions, Rellm.UpdateEvent, Rellm.UpdateEventDetails, Rellm.UpdateOccasions

Used as field type in: GetEventsResponse

message EventInfo

events.proto:163

To be used for ticketing, RSVPs, etc. Stored as JSON in the database.

Used in: Event

enum EventListingType

events.proto:85

The listing type, e.g. `ALL_ACCESSIBLE_EVENTS`, `FOLLOWING_EVENTS`, `MY_GROUPS_EVENTS`, `DIRECT_EVENTS`, `GROUP_EVENTS`, `GROUP_EVENTS_PENDING_MODERATION`. Events returned are ordered by start time unless otherwise specified (specifically, `NEWLY_ADDED_EVENTS`).

Used in: GetEventsRequest

message EventSettings

server_configuration.proto:507

Specific settings for Events.

Used in: ServerConfiguration

message ExpirableToken

authentication.proto:76

Generic type for refresh and access tokens.

Used in: AccessTokenResponse, RefreshTokenResponse

message ExternalCDNConfig

server_configuration.proto:316

Useful for setting your Rellm instance up to run underneath a CDN. By default, the web client uses `window.location.hostname` to determine the backend server. If set, the web client will use this value instead. NOTE: Only applies to Tamagui web client for now.

Used in: ServerConfiguration

message FacebookAuthConfig

federation.proto:79

Facebook authentication configuration for the server.

Used in: FederationInfo

message FacebookPage

sync.proto:75

A Facebook Page connected as a [`SyncDestination`](#rellm-SyncDestination) - **never a personal profile**. Facebook deprecated the `publish_actions` permission in 2018, which was the only way any third-party app could ever post to a personal timeline; there's no Graph API call today, for any app, that can post anything (feed post, photo, or otherwise) to a personal profile on a user's behalf. A Page is the only kind of Facebook entity a self-hosted server like this can post to at all - this isn't a Rellm design choice to work around, it's a hard platform restriction. (Unrelated to this: Facebook *Events* specifically are also unreachable, even for Pages - see `docs/facebook_and_x_twitter_federation.md`'s "It posts to the Page's feed, not a real Facebook Event" for that separate, independent 2018-era lockdown.) Media limitation: a synced Post/Occasion's attached video and images are mutually exclusive on Facebook - if both are present, the video is posted and any images are silently dropped (Facebook Pages can't attach both to a single feed post).

Used in: SyncDestination

message FeatureSettings

server_configuration.proto:459

Settings for a feature (e.g. People, Groups, Posts, Events, Media). Encompasses both the feature's visibility and moderation settings.

Used in: ServerConfiguration

message FederatedAccount

federation.proto:71

Some user on a Rellm server. Most commonly a different server than the one serving up FederatedAccount data, but users may also federate multiple accounts on the same server.

Used as request type in: Rellm.DefederateProfile, Rellm.FederateProfile

Used as response type in: Rellm.FederateProfile

Used as field type in: User

message FederatedServer

federation.proto:58

A server that this server will federate with.

Used in: FederationInfo

message FederationInfo

federation.proto:13

The federation configuration for a Rellm server.

Used in: ServerConfiguration

message Follow

users.proto:129

Model for a user's follow of another user.

Used as request type in: Rellm.CreateFollow, Rellm.DeleteFollow, Rellm.UpdateFollow

Used as response type in: Rellm.CreateFollow, Rellm.UpdateFollow

Used as field type in: User

message FulfillmentNote

market.proto:609

One entry in a MarketSubscription's `fulfillment_notes` -- see that field's own doc. Immutable once appended: `UpdateMarketSubscription` only ever accepts a `fulfillment_notes` list whose existing entries are byte-for-byte identical to what's already stored (see that RPC's own doc).

Used in: RellmHostingSubscriptionDetails

enum FulfillmentStatus

market.proto:64

The state of a `PURCHASE_TYPE_RELLM_HOSTING` order's manual fulfillment -- see `RellmHostingSubscriptionDetails.fulfillment_status`'s own doc for how the "current" value is derived, and `FulfillmentNote.fulfillment_status` for how every transition is recorded as its own timestamped note (a "fulfillment state history"), not just tracked as a bare current value.

Used in: FulfillmentNote, RellmHostingSubscriptionDetails

message GeminiCredentials

ai_providers.proto:256

Credentials for a [Google Gemini API](https://ai.google.dev/gemini-api) connection - the only [`AIProvider.provider`](#rellm-AIProvider) variant currently accepted by [`CreateAIProvider`](#grpc-api-CreateAIProvider)/[`UpdateAIProvider`](#grpc-api-UpdateAIProvider). Used for image generation/editing via Gemini's [Interactions API](https://ai.google.dev/gemini-api/docs/image-generation), e.g. to generate/edit Event posters from an Event's own content - see [`GenerateMedia`](#grpc-api-GenerateMedia).

Used in: AIProvider

enum GetMarketSubscriptionsRequestType

market.proto:166

Used in: GetMarketSubscriptionsRequest

message Group

groups.proto:12

`Group`s are a way to organize users and posts (and thus events). They can be used for many purposes,

Used as request type in: Rellm.CreateGroup, Rellm.DeleteGroup, Rellm.UpdateGroup

Used as response type in: Rellm.CreateGroup, Rellm.UpdateGroup

Used as field type in: GetGroupsResponse

enum GroupListingType

groups.proto:71

The type of group listing to get.

Used in: GetGroupsRequest

message GroupPost

posts.proto:249

A `GroupPost` is a cross-post of a [`Post`](#rellm-Post) to a [`Group`](#rellm-Group). It contains information about the moderation of the post in the group, as well as the time it was cross-posted and the user who did the cross-posting.

Used as request type in: Rellm.CreateGroupPost, Rellm.DeleteGroupPost, Rellm.UpdateGroupPost

Used as response type in: Rellm.CreateGroupPost, Rellm.UpdateGroupPost

Used as field type in: GetGroupPostsResponse, Post

message InstagramAccount

sync.proto:99

An Instagram Business/Creator account connected as a [`SyncDestination`](#rellm-SyncDestination) - **never a personal Instagram account**. Unlike [`FacebookPage`](#rellm-FacebookPage)'s restriction (a *deprecated* permission that used to let apps post to a personal timeline), this one was never possible in the first place: Instagram's Content Publishing API was built from the start only for professional (Business/Creator) accounts, so a personal Instagram account simply has no API surface to post to at all, regardless of what this server does. Posting to Instagram also requires the professional account to be linked to a Facebook Page, so this reuses the same Facebook Login popup and app credentials as [`FacebookPage`](#rellm-FacebookPage) - the server exchanges the token for the Page's access token, then looks up that Page's linked Instagram Business account. Media limitation: only the *first* attached image/video on a synced Post/Occasion is posted - no carousel/multi-image support yet. A post with no media at all is rejected (`instagram_requires_media`) - Instagram's Graph API has no text-only post type.

Used in: SyncDestination

message License

media.proto:254

Grants `licensed_to` access to a `LICENSED` [`Media`](#rellm-Media) item's full-quality bytes. Stored in the `media_licenses` table. A license is *active* while `revoked_at` is unset. (Purchasing licenses through Rellm's Market is not implemented yet.)

message Location

location.proto:6

Locations are places where events can happen.

Used in: Occasion, Rsvps

message MarketPayment

market.proto:267

A single successful charge against a `MarketPurchase` -- one row per completed Stripe `PaymentIntent` (the initial purchase's, or a later renewal's). Backed by the same `market_payments` table as `MarketRefund` (a positive `amount` row marshals to a `MarketPayment`, a negative one to a `MarketRefund` -- see that message's own doc), so a `MarketPayment` is never itself edited or deleted once created; a refund is always its own separate row/message.

Used in: MarketPurchase

message MarketPaymentMethod

market.proto:285

Card details for a MarketPayment, resolved from Stripe at charge time (Stripe's own `PaymentMethod.card` object). Unset entirely if the payment wasn't card-based or details couldn't be resolved. Never carries anything more sensitive than what Stripe itself considers safe to display (brand/last4/expiry) -- never a full card number.

Used in: MarketPayment

message MarketProduct

market.proto:82

An actual subscribable product listed on, say, https://rellm.org/market -- what an admin creates via `CreateMarketProduct`/edits via `UpdateMarketProduct`, and what a buyer actually purchases via `MakeMarketPurchase`. `market_settings.enabled` (see `server_configuration.proto`) gates whether a server's Market is browsable/purchasable at all, independent of any individual product's own `delisted_at`. Listed/delisted by clients by setting `delisted_at` (though the actual date supplied by the client is ignored -- see that field's own doc).

Used as request type in: Rellm.CreateMarketProduct, Rellm.UpdateMarketProduct

Used as response type in: Rellm.CreateMarketProduct, Rellm.UpdateMarketProduct

Used as field type in: GetMarketProductsResponse, MarketPurchase, MarketSubscription

message MarketPurchase

market.proto:216

One completed billing event -- the initial purchase or a later recurring renewal charge -- for a single product. Created only from `web::stripe_webhook` (the initial purchase, on `checkout.session.completed`) or `logic::market::market_renewal` (each subsequent recurring charge), never directly by `MakeMarketPurchase` itself (see that RPC's own doc). MarketPurchases are immutable via the API+CLI once created -- there is no `UpdateMarketPurchase` RPC; the payments, refunds, and (for a subscription) fulfillment information that accumulate against a purchase over time live in their own separate messages/tables instead of ever rewriting this one.

Used in: MarketSubscription

message MarketRefund

market.proto:298

A single refund issued against a `MarketPurchase`'s payment -- see `MarketPayment`'s own doc for how this and `MarketPayment` share the same underlying `market_payments` table.

Used in: MarketPurchase

message MarketRefundMethod

market.proto:317

Same shape as MarketPaymentMethod -- kept as its own message (rather than reusing MarketPaymentMethod directly) since a MarketRefund and the MarketPayment it refunds are otherwise-independent messages, matching the MarketPayment/MarketRefund split itself.

Used in: MarketRefund

message MarketSettings

server_configuration.proto:444

Whether this server's `/market` is open -- an explicit, admin-set toggle independent of `StripeConfig.stripe_enabled` (an admin can configure Stripe credentials without opening the storefront yet, or temporarily close it without touching those credentials). See `ServerConfiguration.market_settings`'s own doc on why this lives outside `StripeConfig`: it's the one bit that has to stay visible to non-admins for federated multi-server Market browsing to work at all.

Used in: ServerConfiguration

message MarketSubscription

market.proto:417

Created for *every* `MarketPurchase`, regardless of `MarketProduct.period` -- a recurring (`PURCHASE_PERIOD_ANNUAL`/`PURCHASE_PERIOD_MONTHLY`) one gets re-billed and re-fulfilled automatically every period by `renew_market_subscriptions.rs` until canceled; a `PURCHASE_PERIOD_INDEFINITE` one-time purchase gets a subscription too (with `renews_at` unset -- see that field's own doc), purely so it's still cancelable and still carries the same per-type fulfillment tracking (e.g. `RellmHostingSubscriptionDetails.fulfillment_status`/`fulfillment_notes`) every other purchase type gets, even though it never actually bills again.

Used as request type in: Rellm.CancelMarketSubscription, Rellm.UpdateMarketSubscription

Used as response type in: Rellm.CancelMarketSubscription, Rellm.UpdateMarketSubscription

Used as field type in: GetMarketSubscriptionsResponse, MarketPurchase, User

message MastodonAccount

sync.proto:121

A Mastodon account connected as a [`SyncDestination`](#rellm-SyncDestination) via a user-supplied Personal Access Token (generated on the user's own instance, under Preferences > Development), rather than an OAuth popup - Mastodon instances are user-chosen arbitrary domains, so there's no single app to register ahead of time the way Facebook/Instagram have one. Media: up to 4 attached images/videos on a synced Post/Occasion are downloaded and re-uploaded as real Mastodon media attachments (any mix of image/video types); a failed individual upload is skipped rather than failing the whole post.

Used in: SyncDestination

message MastodonServer

federation.proto:97

A Mastodon instance this server has a registered OAuth app on. See `FederationInfo.mastodon_servers`.

Used in: FederationInfo

message Media

media.proto:42

A Rellm `Media` message represents a single media item, such as a photo or video. Media data is deliberately *not accessible from the gRPC API*. Instead, the client should fetch media from `http[s]://my.rellm.instance/media/{id}`, unless `url` is set, in which case that URL should be used instead (used for media Rellm doesn't store locally, e.g. from federated ActivityPub/Mastodon or AT Protocol/Bluesky content). Media items may be created with a HTTP POST to `http[s]://my.rellm.instance/media` along with an "Authorization" header (your access token) and a "Content-Type" header. On success, the endpoint will return the media ID in plaintext. `POST /media` supports the following headers: - `Content-Type` - The MIME content type of the media item. - `Filename` - An optional title for the media item. - `Authorization` - Rellm Access Token for the user. Required, but may be supplied in `Cookies`. - `Cookies` - Standard web cookies. The `rellm_access_token` cookie may be used for authentication. `GET /media/{id}` supports the following: - **Headers**: - `Authorization` - Rellm Access Token for the user. May also be supplied in `Cookies` or via query parameter. - `Cookies` - Standard web cookies. The `rellm_access_token` cookie may be used for authentication. - **Query Parameters**: - `authorization` - Rellm Access Token for the user. May also be supplied in the `Cookies` or `Authorization` headers. - Fetching media without authentication requires that it has `GLOBAL_PUBLIC` visibility (or `LICENSED`, if `MediaSettings.licensed_media_visible_globally` is set). Visibility is otherwise enforced like `GetMedia`'s: `SERVER_PUBLIC` for any logged-in user, `LIMITED` for followers of the owner, `PRIVATE` for the owner and admins only. A media item the viewer can't see responds `404`. - Anonymous (unauthenticated) responses omit CORS `Access-Control-Allow-*` headers when the server's `MediaSettings.block_cors_anonymous_media_access` is set, so third-party sites can embed (`<img>`, `<video>`) but not script-read public media. - `LICENSED` media's full-quality bytes are only served to its owner, admins, and users holding an active [`License`](#rellm-License) for it. Everyone else is served its `UNLICENSED_PREVIEW_MEDIUM` conversion (audio/video only; see `MediaMetadata.unlicensed_preview_start_ms`), or `403 Forbidden` if none exists yet.

Used as request type in: Rellm.DeleteMedia, Rellm.DeleteMediaSizes, Rellm.UpdateMedia

Used as response type in: Rellm.DeleteMediaSizes, Rellm.GenerateMedia, Rellm.UpdateMedia

Used as field type in: GetMediaResponse

enum MediaConversion

media.proto:101

Which stored copy of a `Media` item's bytes a `MediaSize` represents.

Used in: MediaSize

message MediaMetadata

media.proto:166

Free-form metadata about a [`Media`](#rellm-Media) item that isn't queried/filtered on, so doesn't need its own columns.

Used in: Media, MediaReference

message MediaReference

media.proto:268

A reference to a media item, designed to be included in other messages as a reference. Contains the bare minimum data needed to fetch media via the HTTP API and render it, and the media item's name (for alt text usage).

Used in: Author, Group, License, Post, User, UserAttendee

message MediaSettings

server_configuration.proto:359

Media is a special type and less customizable than "Features."

Used in: ServerConfiguration

message MediaSize

media.proto:85

One stored copy of a `Media` item's bytes -- either its untouched original upload (`MEDIA_CONVERSION_ORIGINAL`) or an auto-generated resized copy, as produced by the `convert_media_sizes` background job. Fields are tracked per-size (rather than once on `Media` itself) so that a future conversion producing a different kind of derived copy -- e.g. a `image/jpeg` poster frame for a `video/mp4` original, or a differently-cropped aspect ratio -- can vary any of them independently of the original.

Used in: Media, MediaReference

message MediaStoragePurchaseDetails

market.proto:335

`MarketPurchase.details`' `PURCHASE_TYPE_MEDIA_STORAGE` variant -- copied verbatim from the originating `MarketProduct.details` at the moment this purchase was fulfilled (see `MarketPurchase.details`' own doc). Field-for-field identical to `MediaStorageSubscriptionDetails` -- kept as its own message only so the Purchase- and Subscription-side `oneof`s stay independent Rust types (see `logic::market::market_fulfillment::terminate_entitlement`'s own doc for why that distinction matters for `PermissionsAccessPurchaseDetails`/`PermissionsAccessSubscriptionDetails`).

Used in: MarketPurchase

message MediaStorageSubscriptionDetails

market.proto:494

`MarketProduct.details`/`MarketSubscription.details`' `PURCHASE_TYPE_MEDIA_STORAGE` variant -- what a media storage product actually grants. Field-for-field identical to `MediaStoragePurchaseDetails` -- see that message's own doc for why it's still a distinct type.

Used in: MarketProduct, MarketSubscription

message Member

groups.proto:91

Used when fetching group members using the [`GetMembers`](#grpc-api-GetMembers) RPC.

Used in: GetMembersResponse

message Membership

users.proto:145

Model for a user's membership in a group. Memberships are generically included as part of User models when relevant in Rellm, but UIs should use the group_id to reconcile memberships with groups.

Used as request type in: Rellm.CreateMembership, Rellm.DeleteMembership, Rellm.UpdateMembership

Used as response type in: Rellm.CreateMembership, Rellm.UpdateMembership

Used as field type in: Group, Member, User

message Message

messages.proto:23

A Rellm `Message` represents a single message/email sent to one or more recipients (really, "zero or more", as the design incorporates undeliverable messages).

Used as response type in: Rellm.SendMessage

Used as field type in: GetMessagesResponse

enum MessageListingType

messages.proto:199

Used in: GetMessagesRequest

message MessageRead

messages.proto:65

Records that a user has read a particular Message - one row (conceptually; see the composite `message_id`/`user_id` key on the backing table) per (Message, user) that's ever been marked read. Only ever surfaced back to the user it belongs to, as `Message.current_user_read` - there's no RPC to see *other* users' read status on a Message.

Used in: MarkMessagesReadResponse, Message

message MessagingGroup

messages.proto:105

A group of users who are participating in a conversation. Most servers will probably have a (dynamically created) "empty group" for an email like `not_a_user@my_rellm_instance.com`.

Used in: Message

enum Moderation

visibility_moderation.proto:66

Nearly everything in Rellm has one or more `Moderation`s on it. From a high level: - A [`User`](#rellm-User) has a `moderation` that determines whether they can log in (and their visibility per their `visibility`). (This is poorly enforced currently! Fix it if you want!) - This is managed by `people_settings.default_moderation` in [`ServerConfiguration`](#rellm-ServerConfiguration). A default of `UNMODERATED` means that all users can log in. A default of `PENDING` means that all users must be approved by a moderator/admin before they can log in. - A [`Follow`](#rellm-Follow) has a `target_user_moderation` that determines whether the [`User`](#rellm-User) is following the [`Group`](#rellm-Group). - It is managed by `default_follow_moderation` in the targeted [`User`](#rellm-User). - A [`Group`](#rellm-Group) has a `moderation` that determines whether the [`Group`](#rellm-Group) is visible to users (per its `visibility`). - This is managed by `group_settings.default_moderation` in [`ServerConfiguration`](#rellm-ServerConfiguration). - A [`Membership`](#rellm-Membership) has a `group_moderation` and `user_moderation` that determine whether the [`Group`](#rellm-Group) admins and/or the invited user has approved the [`Membership`](#rellm-Membership), respectively. - User invites to [`Group`](#rellm-Group)s (i.e. the `user_moderation`) always start as `PENDING`. The group side of this is managed by `default_membership_moderation` of the [`Group`](#rellm-Group) in question. - A [`Post`](#rellm-Post) has a `moderation` that determines whether the [`Post`](#rellm-Post) is visible to users (per its `visibility`). - This is managed by `post_settings.default_moderation` in [`ServerConfiguration`](#rellm-ServerConfiguration). - A [`GroupPost`](#rellm-GroupPost) has a `moderation` that determines whether the admins/mods of the [`Group`](#rellm-Group) has approved the [`Post`](#rellm-Post) (or [`Post`](#rellm-Post)-descended thing like [`Event`](#rellm-Event)s). - [`Event`](#rellm-Event)s and further objects contain a [`Post`](#rellm-Post) and thus inherit its `moderation` and related [`GroupPost`](#rellm-GroupPost) behavior, for "Group Events."

Used in: EventInfo, EventSettings, FeatureSettings, Follow, GetMembersRequest, Group, GroupPost, Media, MediaSettings, Membership, Post, PostSettings, Rsvp, User

The default navigation tabs in Rellm's Elm UI.

Used in: CustomHomePage, CustomNavigationTab

How a nav tab's icon and title are shown together, if at all. Applies uniformly to every tab (`tabs` above, or the predefined `EVENTS_TAB`/`POSTS_TAB`/`PEOPLE_TAB`/`ABOUT_TAB` set when `tabs` itself is unset) - there's no per-tab override.

Used in: CustomNavigationTabSet

message Occasion

events.proto:188

The time-based component of an [`Event`](#rellm-Event). Has a `starts_at` and `ends_at` time, a [`Location`](#rellm-Location), and an optional [`Post`](#rellm-Post) (and discussion thread) specific to this particular `Occasion` in addition to the parent [`Event`](#rellm-Event).

Used as response type in: Rellm.SyncOccasion

Used as field type in: Event

message OccasionInfo

events.proto:221

To be used for ticketing, RSVPs, etc. Stored as JSON in the database.

Used in: Occasion

message OccasionRsvpInfo

events.proto:228

Consolidated type for RSVP info for an [`Occasion`](#rellm-Occasion). Curently, the `optional` counts below are *never* returned by the API.

Used in: OccasionInfo

message OpenAICredentials

ai_providers.proto:269

Credentials for an [OpenAI API](https://platform.openai.com/docs/api-reference) connection, accepted by [`CreateAIProvider`](#grpc-api-CreateAIProvider)/[`UpdateAIProvider`](#grpc-api-UpdateAIProvider). Used for image generation/editing via OpenAI's [Images API](https://platform.openai.com/docs/guides/image-generation) (the GPT Image model family) - same use case as [`GeminiCredentials`](#rellm-GeminiCredentials), see [`GenerateMedia`](#grpc-api-GenerateMedia).

Used in: AIProvider

enum Permission

permissions.proto:10

Rellm Permissions are a set of permissions that can be granted directly to [`User`](#rellm-User)s and [`Membership`](#rellm-Membership)s. (A [`Membership`](#rellm-Membership) is the link between a [`Group`](#rellm-Group) and a [`User`](#rellm-User).) Subsets of these permissions are also applicable to anonymous users via [`anonymous_user_permissions` in `ServerConfiguration`](#rellm-ServerConfiguration), and to Group non-members via [`non_member_permissions` in `Group`](#rellm-Group), as well as others documented there.

Used in: Author, Group, Membership, PermissionsAccessPurchaseDetails, PermissionsAccessSubscriptionDetails, ServerConfiguration, User, UserAttendee

message PermissionsAccessPurchaseDetails

market.proto:395

`MarketPurchase.details`' `PURCHASE_TYPE_PERMISSIONS_ACCESS` variant -- copied verbatim from the originating `MarketProduct.details` at the moment this purchase was fulfilled. Field-for-field identical to `PermissionsAccessSubscriptionDetails`, but kept as a genuinely distinct Rust type (not just documentation) -- see `logic::market::market_fulfillment::terminate_entitlement`'s own doc, which parses a `MarketSubscription`'s `details` as `PermissionsAccessSubscriptionDetails` specifically (never this message) when clawing back a lapsed grant.

Used in: MarketPurchase

message PermissionsAccessSubscriptionDetails

market.proto:507

`MarketProduct.details`/`MarketSubscription.details`' `PURCHASE_TYPE_PERMISSIONS_ACCESS` variant -- what a permissions-bundle product actually grants. Field-for-field identical to `PermissionsAccessPurchaseDetails` -- see that message's own doc for why it's still a distinct type (that distinction is exactly what lets `logic::market::market_fulfillment::terminate_entitlement` tell "what to claw back" apart from "what was originally billed").

Used in: MarketProduct, MarketSubscription

message Post

posts.proto:137

A `Post` is a message that can be posted to the server. Its `visibility` as well as any associated [`GroupPost`](#rellm-GroupPost)s and [`UserPost`](#rellm-UserPost)s determine what users see it and where. `Post`s are also a fundamental unit of the system. They provide a building block of Visibility and Moderation management that is used throughout Posts, Replies, Events, and Occasions.

Used as request type in: Rellm.CreatePost, Rellm.DeletePost, Rellm.StarPost, Rellm.StreamReplies, Rellm.UnstarPost, Rellm.UpdatePost

Used as response type in: Rellm.CreatePost, Rellm.DeletePost, Rellm.StarPost, Rellm.StreamReplies, Rellm.SyncPost, Rellm.UnstarPost, Rellm.UpdatePost

Used as field type in: Event, GetPostsResponse, Occasion

enum PostContext

posts.proto:103

Differentiates the context of a Post, as in Rellm's data models, Post is the "core" type where Rellm consolidates moderation and visibility data and logic.

Used in: GetPostsRequest, Post

enum PostListingType

posts.proto:68

A high-level enumeration of general ways of requesting posts.

Used in: GetPostsRequest

enum PostMediaLayout

posts.proto:241

Used in: Post

message PostSettings

server_configuration.proto:481

Specific settings for Posts.

Used in: ServerConfiguration

enum PrivateUserStrategy

server_configuration.proto:559

Strategy when a user sets their visibility to `PRIVATE`.

Used in: ServerConfiguration

enum PurchasePeriod

market.proto:45

How often a `MarketProduct`/`MarketSubscription` bills. Immutable on a `MarketProduct` once created (see that message's own doc) -- same reasoning as `PurchaseType`'s immutability.

Used in: MarketProduct, MarketSubscription

enum PurchaseType

market.proto:14

What a `MarketProduct`/`MarketPurchase`/`MarketSubscription` actually grants the buyer once fulfilled -- see `logic::market::market_fulfillment::fulfill_purchase` (the Rust match on this same enum) for exactly what each value does. Immutable on a `MarketProduct` once created (see that message's own doc) -- changing what a product *is* after people have already bought it would silently change existing buyers' entitlements out from under them, so a product whose type needs to change is delisted and replaced with a new one instead.

Used in: MarketProduct, MarketPurchase, MarketSubscription

message RefreshTokenMetadata

authentication.proto:117

Metadata on a refresh token for the current user, used when managing refresh tokens as a user. Does not include the token itself.

Used in: UserRefreshTokensResponse

message RefreshTokenResponse

authentication.proto:65

Returned when creating an account, logging in, or creating a third-party refresh token.

Used as response type in: Rellm.CreateAccount, Rellm.Login

message RellmHostingPurchaseDetails

market.proto:363

`MarketPurchase.details`' `PURCHASE_TYPE_RELLM_HOSTING` variant -- unlike the other three `*PurchaseDetails` messages, NOT copied from the originating `MarketProduct.details`; instead built fresh at checkout time from the buyer's own `MakeMarketPurchaseRequest.rellm_hosting_details` (carried through as Stripe Checkout Session metadata -- see `rpcs::market::make_market_purchase` and `web::stripe_webhook::handle_checkout_session_completed`). Field-for-field identical to `RellmHostingSubscriptionDetails` (minus that message's `fulfillment_status`/`fulfillment_notes`) -- see `MediaStoragePurchaseDetails`'s own doc for why it's still a separate message.

Used in: MakeMarketPurchaseRequest, MarketPurchase

message RellmHostingSubscriptionDetails

market.proto:561

`MarketProduct.details`/`MarketSubscription.details`' `PURCHASE_TYPE_RELLM_HOSTING` variant -- what a dedicated-hosting product actually grants, plus the buyer's own request details and the admin's own fulfillment tracking for it. Field-for-field identical to `RellmHostingPurchaseDetails` for the first five fields (see that message's own doc); `fulfillment_status`/`fulfillment_notes` below have no `*PurchaseDetails` counterpart, since they're only ever meaningful on the standing `MarketSubscription`, not on any one individual `MarketPurchase` billing event.

Used in: MarketProduct, MarketSubscription

message Rsvp

events.proto:313

An RSVP: describes a user's attendance at an [`Occasion`](#rellm-Occasion). Such as: * A user's RSVP to an [`Occasion`](#rellm-Occasion) (one of `INTERESTED`, `GOING`, `NOT_GOING`, or , `REQUESTED` (i.e. invited)). * Invitation status of a user to an [`Occasion`](#rellm-Occasion). * [`ContactMethod`](#rellm-ContactMethod)-driven management for anonymous RSVPs to an [`Occasion`](#rellm-Occasion).

Used as request type in: Rellm.DeleteRsvp, Rellm.UpsertRsvp

Used as response type in: Rellm.UpsertRsvp

Used as field type in: Occasion, Rsvps

enum RsvpStatus

events.proto:259

Occasion RSVP statuses. State transitions may generally happen in any direction, but: * `REQUESTED` can only be selected if another user invited the user whose RSVP is being described. * `GOING` and `NOT_GOING` cannot be selected if the Occasion has ended (end time is in the past). * `WENT` and `DID_NOT_GO` cannot be selected if the Occasion has not started (start time is in the future). `INTERESTED` and `REQUESTED` can apply regardless of whether an event has started or ended.

Used in: GetEventsRequest, Rsvp

message Rsvps

events.proto:282

Response to get RSVP data for an event.

Used as response type in: Rellm.GetRsvps

Used as field type in: Occasion

message ServerColors

server_configuration.proto:779

Color in ARGB hex format (i.e `0xAARRGGBB`).

Used in: ServerInfo

message ServerConfiguration

server_configuration.proto:11

Configuration for a Rellm server instance.

Used as request type in: Rellm.ConfigureServer

Used as response type in: Rellm.ConfigureServer, Rellm.GetServerConfiguration

message ServerInfo

server_configuration.proto:572

User-facing information about the server displayed on the "about" page.

Used in: ServerConfiguration

Logo data for the server. Built atop Rellm [`Media` APIs](#rellm-Media).

Used in: ServerInfo

message StalwartConfig

server_configuration.proto:951

Stalwart is an extablished, open-source Rust email/contact/calendar server (think an Outlook or Google Workspace competitor). Currently Rellm supports receiving emails via Stalwart webhook configurations.

Used in: ServerConfiguration

message StripeConfig

server_configuration.proto:933

Stripe credentials backing the Marketplace (`market.proto`). Used both to create Checkout Sessions/off-session renewal PaymentIntents (`stripe_secret_key`) and to verify incoming webhook deliveries (`stripe_webhook_signing_secret`).

Used in: ServerConfiguration

message SyncDestination

sync.proto:12

A user-owned destination to sync (cross-post) content out to. Mirrors [`SyncSource`](#rellm-SyncSource), but for pushing content out rather than pulling content in. Originally Event-specific (as `EventSyncDestination`), now shared by both [`Occasion`](#rellm-Occasion)s (see `events.proto`'s [`SyncOccasionRequest`](#rellm-SyncOccasionRequest)) and [`Post`](#rellm-Post)s (see `posts.proto`'s [`SyncPostRequest`](#rellm-SyncPostRequest)).

Used as request type in: Rellm.CreateSyncDestination, Rellm.UpdateSyncDestination

Used as response type in: Rellm.CreateSyncDestination, Rellm.UpdateSyncDestination

Used as field type in: DeleteSyncDestinationRequest, GetSyncDestinationsResponse, User

message SyncDestinationStatus

sync.proto:207

The status of a single piece of content's (an [`Occasion`](#rellm-Occasion) or [`Post`](#rellm-Post)) sync (cross-post) to one [`SyncDestination`](#rellm-SyncDestination). Shared/generic so both `Occasion.sync_destinations` and `Post.sync_destinations` can reuse it.

Used in: Occasion, Post

message SyncSource

sync.proto:231

A user-owned source to sync events from.

Used as request type in: Rellm.CreateSyncSource, Rellm.UpdateSyncSource

Used as response type in: Rellm.CreateSyncSource, Rellm.UpdateSyncSource

Used as field type in: DeleteSyncSourceRequest, GetSyncSourcesResponse, Post, User

enum SyncSourceUserAgent

sync.proto:221

The HTTP `User-Agent` a [`SyncSource`](#rellm-SyncSource) sends when fetching its feed. Some feeds sit behind bot protection that only admits certain clients (e.g. raleighnc.gov's Cloudflare rules let `curl/*` through and challenge everything else, including a request with no `User-Agent` at all).

Used in: SyncSource

message TelnyxConfig

server_configuration.proto:874

Telnyx (https://telnyx.com) Config -- an alternative SMS verification provider to [`TwilioConfig`](#rellm-TwilioConfig), with a simpler single-API-key auth model like [`BirdConfig`](#rellm-BirdConfig)'s. See `docs/contact_integrations.md` for full setup steps.

Used in: ServerConfiguration

message ThreadsAccount

sync.proto:194

A connected Threads account - **a genuinely personal account works fine here**, unlike [`FacebookPage`](#rellm-FacebookPage)/[`InstagramAccount`](#rellm-InstagramAccount): the Threads API (a separate product from Instagram's, launched 2024) has no Page-linkage or Business/Creator-account requirement at all - Threads OAuth directly authorizes whatever single Threads account the user logs in with, personal or not. It's still a product added to this server's existing Meta App (see [`FacebookAuthConfig`](#rellm-FacebookAuthConfig)) rather than a separately-registered app, so no separate auth config is needed. Unlike [`FacebookPage`](#rellm-FacebookPage)/[`InstagramAccount`](#rellm-InstagramAccount), connecting one is a `response_type=code` OAuth flow at threads.net (not facebook.com) with no "choose a Page" step - the code is exchanged server-side for a short-lived token, then a long-lived one (~60 day expiry, refreshable via `grant_type=th_refresh_token` - not yet implemented; a connected destination will need reconnecting after ~60 days until a refresh job exists). Media limitation: only the *first* attached image/video on a synced Post/Occasion is posted - no carousel/multi-image support yet. Unlike [`InstagramAccount`](#rellm-InstagramAccount), a text-only post (no media at all) is valid.

Used in: SyncDestination

message TimeFilter

events.proto:71

Time filter that works on the `starts_at` and `ends_at` fields of [`Occasion`](#rellm-Occasion). API currently only supports `ends_after`.

Used in: GetEventsRequest

message TwilioConfig

server_configuration.proto:835

Twilio credentials, authenticated via a Twilio **API Key** (`twilio_api_key_sid`/ `twilio_api_key_secret`) -- deliberately *not* the account's own Auth Token. A server's Auth Token is a single unscoped, unrevocable-without-rotating-everything credential with full access to the whole Twilio account; an API Key is its own separate, individually-revocable credential pair meant for exactly this kind of integration. `twilio_account_sid` is still required (Twilio resource URLs are always addressed by the actual Account SID), but it is *not* used to authenticate -- only the API Key SID/Secret pair is. See https://www.twilio.com/docs/iam/api-keys/restricted-api-keys for the recommended permission when creating one: `/twilio/messaging/messages/create` (nothing else is needed just to send verification SMS). See `docs/contact_integrations.md` for full setup steps (API key creation, inbound webhook registration), and [`ContactMethod`](#rellm-ContactMethod) (`users.proto`) for how a verified `tel:` contact method surfaces this provider.

Used in: ServerConfiguration

message User

users.proto:16

Model for a Rellm user. This user may have [`Media`](#rellm-Media), [`Group`](#rellm-Group) [`Membership`](#rellm-Membership)s, [`Post`](#rellm-Post)s, [`Event`](#rellm-Event)s, and other objects associated with them.

Used as request type in: Rellm.DeleteUser, Rellm.GetAIProviders, Rellm.GetSyncDestinations, Rellm.GetSyncSources, Rellm.UpdateUser

Used as response type in: Rellm.GetCurrentUser, Rellm.UpdateUser

Used as field type in: GetUsersResponse, Member, RefreshTokenResponse

message UserAttendee

events.proto:361

Wire-identical to [Author](#rellm-Author), but with a different name to avoid confusion.

Used in: Rsvp

enum UserListingType

users.proto:290

Ways of listing users.

Used in: GetUsersRequest

message UserPost

posts.proto:266

A `UserPost` is a "direct share" of a [`Post`](#rellm-Post) to a [`User`](#rellm-User). Currently unused/unimplemented. See also: [`DIRECT` `Visibility`](#rellm-Visibility).

message UserRefreshTokensResponse

authentication.proto:110

Response for `GetUserRefreshTokens` RPC. Returns all refresh tokens associated with the current user.

enum Visibility

visibility_moderation.proto:23

Visibility in Rellm is a complex topic. There are several different types of visibility, and each type of entity ([`User`](#rellm-User), [`Media`](#rellm-Media), [`Group`](#rellm-Group), then [`Post`](#rellm-Post)/[`Event`](#rellm-Event)/etc. with common logic) has different rules for visibility. From the top down, the rules break down as follows: - Even a `PRIVATE` entity is always visible to the user who owns it. - For [`Group`](#rellm-Group)s, this means all full members of the [`Group`](#rellm-Group). - For [`User`](#rellm-User)s, this is confusing and there is a whole [`PrivateUserStrategy`](#rellm-PrivateUserStrategy) thing in [`ServerConfiguration`](#rellm-ServerConfiguration) for this. - A `LIMITED` entity is visible to to the owner(s) and any explicitly associated [`User`](#rellm-User)s and [`Group`](#rellm-Group)s. Generally, this only applies to [`Post`](#rellm-Post)/[`Event`](#rellm-Event)/etc. entities. Associations exist via [`UserPost`](#rellm-UserPost)s and [`GroupPost`](#rellm-GroupPost)s. - This is currently only implemented for [`Group`](#rellm-Group)s and [`GroupPost`](#rellm-GroupPost)s. There are some choices to be made about how to implement this for [`User`](#rellm-User)s and [`UserPost`](#rellm-UserPost)s, and whether `DIRECT` should be a separate visibility type. - A `SERVER_PUBLIC` entity is visible to all authenticated users. - A `GLOBAL_PUBLIC` entity is visible to the open internet.

Used in: ContactMethod, EventSettings, FeatureSettings, Group, Media, MediaReference, MediaSettings, Post, PostSettings, User

message WebPushConfig

server_configuration.proto:793

Web Push (VAPID) configuration for the server.

Used in: ServerConfiguration

enum WebUserInterface

server_configuration.proto:612

Offers a choice of web UIs. Generally though, React/Tamagui is a century ahead of Flutter Web, so it's the default.

Used in: ServerInfo

message XTwitterAccount

sync.proto:162

An X (Twitter) account connected as a [`SyncDestination`](#rellm-SyncDestination), via an OAuth 2.0 Authorization Code + PKCE flow at x.com. Requires this server to have a registered X Developer App configured (see `FederationInfo.x_twitter_auth_config`) - every RPC touching an `XTwitterAccount` destination fails with `x_twitter_app_not_configured` until an admin sets one, mirroring [`FacebookAuthConfig`](#rellm-FacebookAuthConfig)/`facebook_app_not_configured`. Unlike Facebook/Instagram/Threads (which reuse one Meta App), an admin registers this app once and every user on the server connects their own X account through it - no per-user API keys needed. Media limitation: up to 4 attached *images* on a synced Post/Occasion are downloaded and re-uploaded via X's media upload endpoint. Video is not yet supported - X's video upload requires a chunked upload-and-processing flow (mirroring Bluesky's own documented video gap) not yet built; a video attachment is silently skipped.

Used in: SyncDestination

message XTwitterAuthConfig

federation.proto:88

X (Twitter) authentication configuration for the server. See `FederationInfo.x_twitter_auth_config`.

Used in: FederationInfo