Skip to content

Architecture Overview

Softly is split into two repositories that communicate over a JSON REST API.

System Diagram

┌─────────────────────┐         ┌──────────────────────┐
│   Mobile (Expo)     │         │   Backend (Rails)    │
│                     │  HTTP   │                      │
│  React Native +     │◄───────►│  Rails 8.1 API       │
│  TypeScript         │  JSON   │  PostgreSQL          │
│  Expo Router        │         │  Solid Queue         │
│                     │         │                      │
│  RevenueCat SDK ────┼────────►│  RevenueCat Webhook  │
│  Deepgram WS ──────┼──┐      │                      │
│  expo-notifications │  │      │  Expo Push API ──────┼──► Apple/Google
│                     │  │      │  Anthropic API ──────┼──► Claude Haiku
└─────────────────────┘  │      │  Deepgram API ───────┼──► Temp keys
                         │      └──────────────────────┘

                         └──► Deepgram (direct streaming)

Repositories

RepoTechPurpose
ai-softlyRails 8.1, PostgreSQL, Solid QueueAPI server, background jobs, AI orchestration
ai-softly-frontendExpo (React Native), TypeScriptiOS and Android mobile app

Key Design Decisions

API-only Rails -- No views, no assets. The backend is purely a JSON API consumed by the mobile app.

Solid Queue over Redis -- Background jobs use Solid Queue (database-backed) instead of Sidekiq/Redis. One fewer infrastructure dependency.

Client-side Deepgram -- Voice audio streams directly from the device to Deepgram via WebSocket. The backend only issues temporary API keys. This avoids proxying audio through the server.

RevenueCat for billing -- All App Store/Play Store interactions are handled by RevenueCat. The backend only syncs the resulting subscription state.

React Query for state -- The mobile app uses React Query (TanStack Query) for server state instead of Redux or Zustand. Cache invalidation and optimistic updates are handled through query keys.

File-based routing -- Expo Router provides file-based routing similar to Next.js. Screen paths map directly to files in the app/ directory.

Data Flow

  1. User interacts with the mobile app
  2. React Query fires an API request (via Axios in services/api.ts)
  3. JWT access token is attached via interceptor
  4. Rails controller processes the request, interacts with models/services
  5. JSON response is returned
  6. React Query caches the response and re-renders affected components

For background work (push notifications, subscription sync, weekly digest), Solid Queue processes jobs asynchronously.

Internal documentation — not for public distribution