← All posts
Case Study Mental Health App Development Wellness App Development React Native Expo AI

Building Sentira: An AI-Powered Mental Wellness App with Streaming Chat & Subscriptions

March 10, 2026· 9 min read

Overview

Sentira is an AI-powered mental wellness companion for iOS and Android, and a real-world example of how to develop a mental health app that people actually stick with. It combines daily journaling, mood tracking, guided check-ins, and a streaming AI chat companion — all behind a subscription paywall.

The product required serious infrastructure: three OAuth providers, Stripe in-app purchases, a real-time streaming API, and a production EAS Build pipeline for both platforms.

"Journal, reflect, and grow — every day."

The Challenge

Mental wellness apps live or die on trust and retention. The technical challenges reflected this:

  1. Multi-provider auth — support email/password, Google (native SDK), and Facebook OAuth without friction
  2. Subscription infrastructure — Apple Pay / Google Pay–integrated Stripe subscriptions with proper paywall logic
  3. Streaming AI chat — conversational AI responses that stream token-by-token, not dump all at once
  4. Keyboard-aware UX — journaling and chat are keyboard-heavy; poor keyboard handling destroys the experience

Full-Stack Architecture

graph TB
    subgraph Mobile["Mobile App (Expo)"]
        UI[React Native UI<br/>NativeWind]
        JO[Jotai State]
        TQ[TanStack Query]
    end

    subgraph Backend["Backend (Node.js / Bun)"]
        API[REST API]
        AI[AI Chat Service<br/>Streaming]
        STRIPE[Stripe Webhooks]
    end

    subgraph Infra["Infrastructure"]
        SB[(Supabase<br/>PostgreSQL + Auth)]
        STRIPE_API[Stripe API]
        LLM[LLM Provider]
    end

    UI --> JO
    UI --> TQ
    TQ --> API
    API --> SB
    API --> AI
    AI --> LLM
    STRIPE --> API
    API --> STRIPE_API

    style Mobile fill:#1A1A2E,stroke:#7DFF00,color:#fff
    style Backend fill:#0D0D18,stroke:#7DFF00,color:#fff
    style Infra fill:#1A1A2E,stroke:#8A8A9A,color:#fff

The backend runs on Node.js with Bun runtime — fast startup, native TypeScript, and compatible with the Bun workspace monorepo.

Authentication Architecture

Getting three OAuth providers working natively — especially Google on iOS and Android — requires careful setup. Here's the auth flow:

flowchart TD
    START([User opens app]) --> ONBOARDING[Onboarding flow]
    ONBOARDING --> CHOICE{Sign-in method}

    CHOICE -->|Email| EMAIL[Email + password form]
    CHOICE -->|Google| GOOGLE[Google native SDK<br/>@react-native-google-signin]
    CHOICE -->|Facebook| FACEBOOK[Facebook OAuth<br/>WebView flow]

    EMAIL --> VERIFY[Email verification]
    GOOGLE --> TOKEN[Google ID token]
    FACEBOOK --> CODE[OAuth code]

    VERIFY --> SUPABASE[(Supabase Auth)]
    TOKEN --> SUPABASE
    CODE --> SUPABASE

    SUPABASE --> SESSION[JWT session]
    SESSION --> APP[Authenticated app]

    style START fill:#0D0D18,stroke:#7DFF00,color:#fff
    style APP fill:#1A1A2E,stroke:#7DFF00,color:#fff
    style SUPABASE fill:#0D0D18,stroke:#7DFF00,color:#fff

Key implementation decisions:

Subscription & Paywall

stateDiagram-v2
    [*] --> Free: New user
    Free --> Paywall: Hit feature gate
    Paywall --> Checkout: Tap subscribe
    Checkout --> Processing: Apple Pay / Google Pay
    Processing --> Active: Payment success
    Processing --> Free: Payment failed / cancelled
    Active --> Cancelled: User cancels
    Cancelled --> Grace: Grace period
    Grace --> Free: Period expired
    Active --> Active: Renewal

The subscription system uses Stripe React Native with:

Streaming AI Chat

The AI chat companion streams responses token-by-token. The user sees the reply building character by character — not a loading spinner followed by a wall of text.

sequenceDiagram
    participant User
    participant App as Mobile App
    participant API as Backend API
    participant LLM as LLM Provider

    User->>App: Send message
    App->>API: POST /chat/stream
    API->>LLM: Forward with context
    LLM-->>API: Stream tokens
    API-->>App: SSE stream chunks
    App-->>User: Render tokens in real-time
    LLM-->>API: [DONE]
    API-->>App: Stream close

Implementation: Server-Sent Events (SSE) from the backend, consumed in the React Native app via a streaming fetch. Each token chunk updates the message state, triggering a fast re-render.

Keyboard-Aware UX

Journaling and chat both involve heavy keyboard usage. On React Native, poor keyboard handling is a common and painful bug — content gets obscured, scroll position jumps, or the keyboard dismisses unexpectedly.

We used react-native-keyboard-controller throughout. It provides:

Tech Stack

Layer Choice
Framework Expo (React Native) + Expo Router
Language TypeScript
Backend Node.js (Bun runtime)
Database & Auth Supabase (PostgreSQL + Auth)
State (client) Jotai
State (server) TanStack Query
Styling NativeWind
Payments Stripe React Native + EAS
Auth Providers Supabase OAuth — Google native, Facebook, email
Keyboard react-native-keyboard-controller
Build EAS Build (IPA / AAB)
Monorepo Bun workspaces

Monorepo Structure

sentira/
  apps/
    mobile/     ← Expo app
    api/        ← Node.js backend (Bun)
  packages/
    types/      ← Shared TypeScript types
    config/     ← Shared ESLint, tsconfig

Shared TypeScript types between the mobile app and backend eliminate a whole class of API contract bugs.

Outcome


Building a wellness, health, or consumer subscription app? Let's talk →

Share this post

Want to build something like this?

15 minutes. We'll look at your business and scope what an AI automation built for it would actually do — concrete, no pitch deck.

Follow OffLift