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>
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_configtable — 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_configtable — 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 SvelteKitfosterflow-db— MariaDBfosterflow-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
mysqldumpon 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/petsendpoint - 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 tenantcustomer.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)