Modern technical architecture
Platform Feature

Built for recovery. Designed to scale.

A modern, serverless architecture that handles the complexity of recovery management while maintaining the security and reliability your organization requires.

Modern stack, proven technologies

We chose technologies that are battle-tested at scale, well-documented, and backed by strong communities.

PIR Portal

React 19 + TypeScript + Vite

Native iOS/Android via Capacitor

Admin Portal

React 18 + TypeScript + Vite

Responsive web application

Backend

Firebase + Node.js 20

150+ Cloud Functions

Database

Firestore + 76 collections

44 user + 26 org + 6 Nexus

150+

Cloud Functions

Serverless compute for all backend logic

76

Firestore Collections

Organized in multi-tenant hierarchy

63

Novu Workflows

Multi-channel notification pipelines

3,500+

Indexed Meetings

Full-text search via Typesense

Multi-Tenant

Organization-scoped data isolation

Every piece of client data lives under a hierarchical path that includes the organization ID. This architecture ensures complete data isolation between organizations while allowing for future multi-tenancy.

// User data path

/organizations/{orgId}/users/{userId}/checkIns/...

// Organization collection

/organizations/{orgId}/messages/{docId}

Zero cross-org data leaks
Future white-label ready

Mobile-First Enforcement

The PIR Portal is mobile-only. 29 web routes redirect to app download.

/book — allowed /admin — allowed /crisis-resources — allowed

Data Hierarchy

Firestore Root
/organizations/{orgId}
/users/{userId}

checkIns

goals

activityEvents

...44 collections

Org Collections

messages

conversations

auditLogs

...26 collections

E!A Source-of-Truth Pattern

Write Path

Frontend Cloud Function E!A API

Sync Path

E!A Webhook Firestore

Read Path (Instant)

Frontend onSnapshot Firestore
Event-Driven Sync

Real-time without polling

External services like Easy!Appointments are the source of truth for their data. We sync via webhooks and serve reads from Firestore with onSnapshot — giving users instant updates without expensive API calls.

Webhook idempotency — duplicate events are safely ignored
WebSocket reads — Firestore onSnapshot delivers updates instantly
Cache-aside pattern — Listmonk data cached in Firestore with TTL
Deployment Pipeline

Four hosting targets, one command

Firebase Hosting serves all web properties with automatic SSL, CDN distribution, and atomic deployments.

app

app.glrecoveryservices.com

PIR Portal + Admin Portal

marketing

glrecoveryservices.com

Marketing site

tech

tech.glrecoveryservices.com

Portfolio site

preview

preview-*.web.app

Testing environment

Web Deployment

firebase deploy --only hosting

Deploys all 4 targets atomically

iOS Deployment

npx cap sync ios && xcodebuild...

Wireless deployment via ios-deploy

Self-Hosted Infrastructure

Control your data, control your costs

Critical services run on our own infrastructure — no per-email fees, no per-notification charges, no surprise bills.

Novu

Real-time notifications

Railway

Listmonk

Email marketing

Railway

n8n

Workflow automation

Railway

Easy!Appointments

Booking system

Railway

Typesense

Search engine

Typesense Cloud

All self-hosted services include automated health checks and fail-loud error handling.

Third-Party APIs

Integrated services

Purpose-built integrations with best-in-class external services.

Google Places

Address autocomplete with location biasing

Google Calendar

Milestone sync to personal calendars

Twilio

SMS-based two-factor authentication

OpenAI

GPT-4o insights and weekly summaries

Firebase Extensions

delete-user-data

GDPR compliance — auto-delete on account removal

firestore-pdf-generator

Generate PDFs from templates

firestore-typesense-search

Auto-sync meetings to search index

firestore-typesense-search

Auto-sync resources to search index

Push Notifications

Real-time notification pipeline

Every user action flows through a unified notification pipeline that handles in-app feeds, push notifications, and emails — all from a single event source.

63 Novu workflows — from check-in reminders to milestone celebrations
35+ deep link destinations — tapping opens the right screen
Weekly FCM token refresh — no silent delivery failures

Why self-hosted Novu? No per-notification fees. The same infrastructure that would cost $500+/month with managed services runs on our own Railway instance for a flat monthly rate.

Notification Flow

1

Activity Event

User action triggers event in Firestore

2

Cloud Function

Firestore trigger routes to Novu workflow

3

Novu Processing

63 workflows determine channels and content

4

Delivery

FCM/APNs push + in-app feed + email

Entire pipeline executes in under 2 seconds

Capacitor Bridge Architecture

React App (TypeScript)
Vite Build
Capacitor Bridge
WKWebView (iOS)
426 lines Swift
WebView (Android)
Kotlin config
Native Bridge

True native capabilities

Capacitor bridges the gap between web and native, giving us access to device features like push notifications, camera, haptics, and more — all from TypeScript code.

17 Native Plugins

App Browser Camera Filesystem Haptics Keyboard Local Notifications Network Preferences Push Notifications Share Splash Screen Status Bar Safe Area

5 Notification Action Categories

Users can reply to messages, mark check-ins complete, or log meetings directly from notification banners — without opening the app.

Security by design

Every layer of the stack is designed with security in mind, from database rules to credential management.

Firebase Security Rules

2,000+ lines of hierarchical access control

Custom Claims

Role-based permissions via Auth tokens

Data Isolation

Organization-scoped data paths

Secret Management

Firebase Secrets for all credentials

Monitoring

Automated health checks

Proactive monitoring catches issues before they affect users — not after.

Daily Health Check

Listmonk API tested every 8 AM Pacific

Fail-Loud Pattern

Missing credentials crash fast, not silently

Firebase Logs

Real-time function execution monitoring

Railway Metrics

CPU, memory, and request monitoring

Fail-Loud Credential Pattern

function getCredentials() {
  const key = process.env.API_KEY?.trim();

  if (!key) {
    throw new Error('[FATAL] API_KEY not configured');
  }
  return key;
}

Why fail-loud? Silent failures are worse than crashes. Missing credentials crash fast at deploy time, not silently in production where they go unnoticed.

Questions about our architecture?

Schedule a technical deep-dive with our team. We're happy to discuss security, compliance, and integration requirements.