# 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`) ```sql -- 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: ```sql 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 ```css /* 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: ```svelte
{t('applications.adoption.intro')}
``` `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) ```bash 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: `` 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)