Files
fosterflow/docs/superpowers/specs/2026-03-26-fosterflow-saas-design.md
T
justinandClaude Opus 4.6 d192062793 docs: add tiered pricing model to design spec
Three paid tiers: Manage ($12/mo, admin-only + embeddable widgets),
Cloud ($25/mo, full hosted site), API add-on (+$5/mo). Self-hosted
remains free. Updated revenue projections and billing products.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 13:09:38 -05:00

22 KiB

FosterFlow SaaS Platform — Design Spec

Date: 2026-03-26 Domain: fosterflow.app Status: Approved


Overview

FosterFlow is a multi-tenant SaaS platform for animal rescues. It provides a complete website + management system — everything a foster-based rescue needs, nothing it doesn't. Clean room rebuild using the AHCR (Almost Home Canine Rescue) codebase as reference.

Two deployment modes:

  • Self-hosted — free, open source (MIT), rescue runs the tenant app themselves via Docker
  • FosterFlow Cloud — $19/mo managed hosting with automated provisioning, subdomain, SSL, backups, updates

Tech stack: SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4, Drizzle ORM, MariaDB, Stripe, Nodemailer, Caddy, Ollama (AI features)


1. Overall Architecture

┌─────────────────────────────────────────────────────┐
│                    Caddy Proxy                       │
│  fosterflow.app      → platform app (marketing,     │
│                        signup, operator dashboard)   │
│  *.fosterflow.app    → tenant app (by subdomain)    │
│  custom domains      → tenant app (by domain)       │
└──────────────┬──────────────────────┬───────────────┘
               │                      │
    ┌──────────▼──────────┐ ┌────────▼────────────┐
    │  Platform App       │ │  Tenant Instance(s)  │
    │  (SvelteKit)        │ │  (SvelteKit)         │
    │                     │ │                      │
    │  /           landing│ │  /     public site   │
    │  /signup     form   │ │  /admin  dashboard   │
    │  /operator   mgmt   │ │  /foster portal      │
    │  /api        hooks  │ │  /api    endpoints   │
    └──────────┬──────────┘ └────────┬────────────┘
               │                      │
    ┌──────────▼──────────┐ ┌────────▼────────────┐
    │  fosterflow_platform│ │  ff_{slug} (per DB)  │
    │  (tenants, billing, │ │  (pets, users, apps, │
    │   signups, events)  │ │   donations, etc.)   │
    └─────────────────────┘ └─────────────────────┘
               │                      │
               └──────────┬───────────┘
                    MariaDB Server

Two separate apps, two separate repos:

  • fosterflow (public, GitHub) — the tenant app. Full rescue site + admin + foster portal. This is what self-hosted users clone. MIT licensed.
  • fosterflow-cloud (private) — the platform app. Operator dashboard, signup/approval, provisioning engine, billing. Only runs on FosterFlow infrastructure.

The tenant app has zero knowledge of multi-tenancy. It's just "a rescue site." The platform app orchestrates instances of it.

Shared Ollama instance — platform-level AI service all tenants call for the dog name generator (free perk).


2. Data Model

Platform Database (fosterflow_platform)

-- Signup applications (pre-approval)
CREATE TABLE signups (
    id INT PRIMARY KEY AUTO_INCREMENT,
    org_name VARCHAR(255) NOT NULL,
    admin_name VARCHAR(255) NOT NULL,
    admin_email VARCHAR(255) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    slug VARCHAR(100) NOT NULL UNIQUE,
    website_url VARCHAR(255),
    ein VARCHAR(20),
    description TEXT,
    city VARCHAR(100),
    state VARCHAR(100),
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    reviewed_by INT,
    reviewed_at TIMESTAMP,
    rejection_reason TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Active tenants (post-approval)
CREATE TABLE tenants (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(100) NOT NULL UNIQUE,
    subdomain VARCHAR(100) NOT NULL UNIQUE,
    custom_domain VARCHAR(255),
    db_name VARCHAR(100) NOT NULL,
    port INT NOT NULL,
    admin_email VARCHAR(255) NOT NULL,
    admin_name VARCHAR(255) NOT NULL,
    plan ENUM('trial', 'manage', 'cloud', 'self_hosted') DEFAULT 'trial',
    stripe_customer_id VARCHAR(255),
    stripe_subscription_id VARCHAR(255),
    status ENUM('provisioning', 'active', 'suspended', 'deleted') DEFAULT 'provisioning',
    trial_ends_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Audit trail for tenant lifecycle
CREATE TABLE tenant_events (
    id INT PRIMARY KEY AUTO_INCREMENT,
    tenant_id INT NOT NULL REFERENCES tenants(id),
    event VARCHAR(50) NOT NULL,
    details JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Operator users
CREATE TABLE operators (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(255) NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Tenant Database (ff_{slug})

Group Tables
Auth users, sessions
Pets pets, pet_photos, pet_medical, foster_assignments, pet_mentors
Applications applications, application_homevisit_notes
People vets, mentors, sponsors, intake_sources
Finance donations, expenses
Content events, happy_tails, content_blocks, newsletter_subscribers, contact_messages
Foster foster_submissions, supply_requests
Config site_settings, org_config
System audit_log, page_views, ai_jobs

org_config table — key/value store with JSON values:

CREATE TABLE org_config (
    id INT PRIMARY KEY AUTO_INCREMENT,
    `key` VARCHAR(100) NOT NULL UNIQUE,
    value JSON NOT NULL,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Example rows:
-- ('branding', '{"primaryColor":"#0d9488","accentColor":"#f59e0b","logoUrl":"/uploads/logo.png"}')
-- ('org_info', '{"name":"Almost Home","city":"Sioux Falls","state":"SD","phone":"..."}')
-- ('locale', '{"language":"en","currency":"USD","dateFormat":"MM/DD/YYYY"}')
-- ('applications', '{"adoptionEnabled":true,"fosterEnabled":true,"adoptionFee":250}')
-- ('agreements', '{"adoption":[...],"foster":[...]}')

Changes from AHCR:

  • Added org_config table — drives theming, org details, locale, form settings
  • All user-facing strings from config, not hardcoded
  • Dropped sync_jobs (PetFinder API is dead)
  • Dropped AHCR-specific import tables
  • Generic CSV import replaces hardcoded importers

3. Tenant Lifecycle

Rescue finds fosterflow.app
         │
         ▼
┌─────────────────────┐
│  Signup Form        │
│  org name, admin    │
│  name/email, pass,  │
│  subdomain, website │
│  EIN (opt), desc,   │
│  city/state         │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  Pending Review     │  → Operator gets email notification
│  (stored in         │
│   platform DB)      │
└─────────┬───────────┘
          │
    ┌─────▼─────┐
    │  Operator  │
    │  reviews   │
    ├────┬───────┤
    │    │       │
  Approve    Reject
    │         │
    │         └→ Rejection email with reason
    ▼
┌─────────────────────┐
│  Auto-Provision     │  (~60 seconds)
│  1. Create DB       │
│  2. Run migrations  │
│  3. Seed admin user │
│  4. Generate .env   │
│  5. Clone tenant app│
│  6. Build & start   │
│  7. Add Caddy route │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  Welcome Email      │
│  login URL, temp    │
│  password, guide    │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  Setup Wizard       │
│  branding, config,  │
│  application forms, │
│  optional CSV import│
└─────────┬───────────┘
          │
          ▼
   Live at {slug}.fosterflow.app
   14-day free trial (no card required)
          │
          ▼
   Trial expires → Pay $19/mo → Active
                → Don't pay  → Suspended (7 day grace)
                             → Deleted (30 days, data archived)

Signup validation: subdomain availability, unique email, spam checks.

Suspension: App still runs but shows "account suspended" page. Data preserved. Reactivate by paying.

Custom domains: Tenant sets domain in settings, adds CNAME to {slug}.fosterflow.app, Caddy auto-provisions SSL.

Deprovisioning: Stop service, remove Caddy route, archive DB dump + uploads, mark deleted. Hard delete after 30 days.


4. Roles & Permissions

Operator Level (Platform App)

Role Access
operator Full access — approve/reject signups, manage tenants, billing, health, migrations

Separate auth system from tenant auth. Email/password login.

Tenant Level (Tenant App)

Role Permissions
sysadmin All 14 permissions (full control of rescue instance)
director All except system
foster_coordinator dashboard, pets, applications
volunteer_manager dashboard, volunteers, events
vet_liaison dashboard, pets, vets, medical, expenses
content_editor dashboard, sponsors, content, events
applications_manager dashboard, applications, pets_view
viewer dashboard only
foster Foster portal only

14 permissions: pets, applications, donations, expenses, vets, medical, events, content, volunteers, users, shop, import, system, dashboard

Tenant sysadmin has zero visibility into the platform. They see their rescue site and nothing else.


5. Feature Set

Core Features (Rebuilt from AHCR Reference)

Feature Description
Pet Management Profiles, photos, medical records, foster assignments, status tracking, mentors
Applications Adoption, foster, volunteer, surrender — configurable questions per form
Foster Portal Dedicated dashboard — submit updates, photos, supply requests
Donations & Expenses Stripe payments, multi-source tracking (PayPal, Venmo, cash, etc.), financial reports
Events Listings with optional Facebook auto-posting
Content CMS Editable page sections via content_blocks, happy tails / success stories
Newsletter Subscriber management, compose and send
Contact Forms Public contact form with message management in admin
Sponsors Three tiers (top dog, wagging tails, wet noses), public sponsor page
Analytics Page views, traffic tracking, bot detection
Bot Protection Dynamic robots.txt, honeypot, managed blocklist/whitelist
AI Name Generator Shared Ollama instance, free for all tenants

New for FosterFlow

Feature Description
Setup Wizard First-login onboarding — branding, org info, forms config, optional CSV import
Configurable Theming Primary + accent color, logo upload via CSS custom properties
org_config System All org-level settings in one place, drives the whole app
Generic CSV Import Upload any CSV, map columns to FosterFlow fields, preview before commit
Health Endpoint /api/health for platform monitoring
i18n Architecture Translation key system, English strings in locale files, ready for more languages

Dropped from AHCR

Dropped Why
PetFinder sync API is dead
AHCR-specific CSV import scripts Replaced with generic column-mapping CSV import
Hardcoded content Replaced with CMS content blocks + org_config

6. Tech Architecture Details

Tenant App Structure

fosterflow/
├── src/
│   ├── lib/
│   │   ├── server/
│   │   │   ├── db.ts              # Drizzle + MariaDB connection
│   │   │   ├── schema.ts          # All tenant tables
│   │   │   ├── auth.ts            # Session management, Argon2
│   │   │   ├── auth-utils.ts      # Permission checks
│   │   │   ├── email.ts           # Nodemailer (from address via config)
│   │   │   ├── stripe.ts          # Payment processing
│   │   │   ├── upload.ts          # File uploads
│   │   │   ├── audit.ts           # Audit logging
│   │   │   ├── rate-limit.ts      # Rate limiting
│   │   │   ├── content.ts         # CMS content blocks
│   │   │   ├── contract.ts        # Adoption contract generation
│   │   │   ├── ai.ts              # Ollama client (name generator)
│   │   │   └── config.ts          # Reads org_config, caches it
│   │   ├── components/
│   │   │   ├── ui/                # Shared UI primitives
│   │   │   ├── admin/             # Admin dashboard components
│   │   │   └── public/            # Public site components
│   │   ├── i18n/
│   │   │   ├── index.ts           # Translation loader
│   │   │   └── locales/
│   │   │       └── en.json        # English strings
│   │   ├── roles.ts               # Role + permission definitions
│   │   └── slugify.ts
│   ├── routes/
│   │   ├── (public)/              # Public-facing pages
│   │   ├── admin/                 # Admin dashboard
│   │   ├── foster/                # Foster portal
│   │   ├── api/                   # API endpoints
│   │   ├── application/           # Public application forms
│   │   ├── setup/                 # First-login setup wizard
│   │   ├── login/
│   │   ├── logout/
│   │   ├── register/
│   │   ├── change-password/
│   │   ├── pay/                   # Stripe payment pages
│   │   ├── robots.txt/
│   │   └── sitemap.xml/
│   ├── hooks.server.ts            # Auth, security headers, tracking
│   └── app.html
├── drizzle/                       # Migration files
├── scripts/
│   ├── seed.ts                    # Seed placeholder data
│   └── seed-tenant.ts             # Seed admin user (used by provisioner)
├── static/
├── .env.example
├── package.json
├── svelte.config.js
├── drizzle.config.ts
├── vite.config.ts
├── docker-compose.yml
├── Dockerfile
└── Caddyfile

Config-Driven Architecture

Everything flows from two sources:

  • .env — infrastructure config (DB URL, ports, secrets, Stripe keys, SMTP)
  • org_config table — org-level config (branding, org info, locale, form settings, agreements)
Request comes in
  → hooks.server.ts loads org_config (cached, refreshes every 5 min)
  → event.locals.orgConfig available in all routes
  → Components read orgConfig for branding, org name, colors
  → CSS custom properties set from orgConfig.branding
  → i18n loads locale from orgConfig.locale

Theming

/* Generated from org_config.branding */
:root {
  --color-primary: #0d9488;
  --color-accent: #f59e0b;
}

Tailwind CSS 4 picks these up via CSS custom properties. Components use text-primary, bg-accent, etc. No hardcoded colors.

i18n Approach

Every user-facing string goes through a t() function:

<h1>{t('pets.title')}</h1>
<p>{t('applications.adoption.intro')}</p>

en.json ships with the app. Adding a language = adding a locale file + tenant picks locale in settings. No code changes required.

Generic CSV Import

  • Upload any CSV file
  • App auto-detects columns, shows preview
  • User maps their columns to FosterFlow fields (name, breed, age, status, etc.)
  • Preview mapped data before committing
  • Handle mismatches gracefully (skip bad rows, show errors)
  • Available in setup wizard and in admin anytime for bulk imports

7. Testing Strategy

Layer Tool What
Unit Vitest Schema validation, permission checks, config loading, i18n, utility functions
Integration Vitest DB operations, auth flows, API endpoints (real DB, not mocks)
E2E Playwright Full user flows — setup wizard, pet CRUD, application submission, foster portal

Key test scenarios:

  • Setup wizard completes and populates org_config
  • Role permissions block/allow correctly
  • Theming applies from org_config
  • CSV import with various column formats
  • Application forms render based on config
  • Public site reflects CMS content blocks
  • Health endpoint returns correct status

8. Deployment & Infrastructure

Self-Hosted (Tenant App Only)

git clone https://github.com/jrei/fosterflow
cd fosterflow
cp .env.example .env    # edit with your details
docker compose up -d    # MariaDB + app + Caddy

Three containers:

  • fosterflow-app — Node.js SvelteKit
  • fosterflow-db — MariaDB
  • fosterflow-proxy — Caddy (auto SSL)

No operator dashboard. No multi-tenant machinery. Just a rescue site.

FosterFlow Cloud

Caddy proxy, one app process per tenant, database-per-tenant on shared MariaDB. Platform app runs alongside.

Provisioning script automates: create DB, run migrations, seed admin user, clone tenant app, generate .env, build, start systemd service, add Caddy route.

Scaling path:

Tenants Setup
0-100 Single VPS ($15-20/mo)
100-200 Separate DB server, shared app build (symlink not copy), automated health checks
200+ Multiple app servers, Cloudflare R2 for images, monitoring (Grafana), consider containers

Revenue at scale:

Tenants Monthly Revenue Server Costs Margin
10 $190 $15 $175
50 $950 $30 $920
100 $1,900 $60 $1,840
500 $9,500 $200 $9,300

Backups

  • Per-tenant mysqldump on cron
  • Uploads archived separately
  • Platform DB backed up independently
  • 30-day retention after tenant deletion

Pricing Tiers

Tier Price What They Get
Self-Hosted Free Full source code, run it yourself, MIT licensed
Manage $12/mo Admin dashboard + foster portal only. No hosted public site. Embeddable pet widgets for their existing website.
Cloud $25/mo Full hosted website + management + subdomain + SSL + backups + updates
API Add-on +$5/mo REST API access for custom integrations (available on Manage or Cloud)

Trial: 14 days free on any paid tier (no card required)

Why tiered pricing:

  • Most rescues already have a website (WordPress, Facebook page, etc.). The Manage tier captures rescues that want better management tools but don't want to switch their whole site.
  • Embeddable widgets bridge the gap — a JS snippet that shows available pets on their existing site, pulling from FosterFlow data. Displays "Powered by FosterFlow" (marketing for upgrades).
  • Cloud is the premium experience — full hosted site + management, zero setup.
  • API access lets tech-savvy rescues build custom integrations or embed data however they want.

Manage tier details:

  • Tenant gets admin dashboard + foster portal (same app, just no public routes served)
  • Caddy routes their subdomain to admin/foster only
  • Embeddable widgets: <script src="https://{slug}.fosterflow.app/embed/pets.js"></script> renders a pet grid on any page
  • Widget is an iframe or web component pulling from the existing /api/pets endpoint
  • Lower infrastructure cost (no public traffic, just admin users)

Revenue projections (updated):

Tenants Mix (Manage/Cloud) Monthly Revenue Server Costs Margin
10 5/5 $185 $15 $170
50 30/20 $860 $30 $830
100 60/40 $1,720 $60 $1,660
500 300/200 $8,600 $200 $8,400

Billing (Stripe)

  • Products: FosterFlow Manage ($12/mo), FosterFlow Cloud ($25/mo), API Add-on (+$5/mo)
  • Trial: 14 days free (no card required)
  • Webhook events:
    • customer.subscription.created → activate tenant
    • customer.subscription.deleted → suspend tenant (grace period)
    • invoice.payment_failed → warning email, suspend after 3 failures
  • Dunning: Stripe handles retry logic

Reference

  • Domain: fosterflow.app
  • AHCR source: ../almosthomecaninerescue.com-2026/ahcr/
  • Market: ~10,000 rescue organizations in the US, ~14,000 total animal welfare orgs
  • Differentiator: Full website + management in one (competitors are mostly backend-only tools). Manage tier captures rescues that just want the backend.
  • Competition: Shelterluv ($2/adoption), RescueGroups (free), Petstablished (free), BARRK ($20/mo)