# Automated Onboarding ## Flow ``` Rescue signs up at pawprint.app │ ▼ ┌─────────────────────┐ │ Signup Form │ │ - Org name │ │ - Admin email │ │ - Admin name │ │ - Subdomain pick │ │ - Password │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ Validate │ │ - Subdomain avail? │ │ - Email unique? │ │ - Spam check │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ Provision │ ← Automated script │ 1. Create DB │ │ 2. Run migrations │ │ 3. Seed admin user │ │ 4. Generate .env │ │ 5. Create service │ │ 6. Add Caddy route │ │ 7. Start app │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ Welcome Email │ │ - Login URL │ │ - Temp password │ │ - Getting started │ └─────────┬───────────┘ │ ▼ Rescue is live at {slug}.pawprint.app (~60 seconds total) ``` ## Provisioning Script ```bash #!/bin/bash # provision-tenant.sh set -e SLUG=$1 ORG_NAME=$2 ADMIN_EMAIL=$3 ADMIN_NAME=$4 DB_NAME="pp_${SLUG}" PORT=$(next_available_port) # Function to find next open port APP_DIR="/var/www/pawprint/${SLUG}" SESSION_SECRET=$(openssl rand -hex 32) TEMP_PASSWORD=$(openssl rand -hex 4) echo "=== Provisioning ${SLUG} ===" # 1. Create database echo "Creating database ${DB_NAME}..." mysql -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -e "CREATE USER IF NOT EXISTS 'pp_${SLUG}'@'localhost' IDENTIFIED BY '$(openssl rand -hex 16)';" mysql -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO 'pp_${SLUG}'@'localhost';" mysql -e "FLUSH PRIVILEGES;" # 2. Clone app (or symlink to shared build) echo "Setting up app directory..." mkdir -p ${APP_DIR} cp -r /var/www/pawprint/_template/* ${APP_DIR}/ # Or: ln -s /var/www/pawprint/_shared_build/build ${APP_DIR}/build # 3. Generate .env echo "Generating .env..." cat > ${APP_DIR}/.env << EOF DATABASE_URL=mysql://pp_${SLUG}:${DB_PASS}@localhost:3306/${DB_NAME} SESSION_SECRET=${SESSION_SECRET} UPLOAD_DIR=${APP_DIR}/uploads PUBLIC_SITE_URL=https://${SLUG}.pawprint.app ORG_NAME=${ORG_NAME} PORT=${PORT} EOF # 4. Run migrations echo "Running migrations..." cd ${APP_DIR} npx drizzle-kit push # 5. Seed admin user echo "Seeding admin user..." node scripts/seed-tenant.js \ --email "${ADMIN_EMAIL}" \ --name "${ADMIN_NAME}" \ --password "${TEMP_PASSWORD}" \ --orgName "${ORG_NAME}" # 6. Create systemd service echo "Creating systemd service..." cat > /etc/systemd/system/pawprint-${SLUG}.service << EOF [Unit] Description=PawPrint - ${ORG_NAME} After=network.target [Service] Type=simple User=www-data WorkingDirectory=${APP_DIR} ExecStart=/usr/bin/node build Restart=on-failure RestartSec=5 Environment=NODE_ENV=production EnvironmentFile=${APP_DIR}/.env [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable pawprint-${SLUG} systemctl start pawprint-${SLUG} # 7. Add Caddy route echo "Configuring Caddy..." cat >> /etc/caddy/sites/pawprint-tenants.caddy << EOF ${SLUG}.pawprint.app { reverse_proxy localhost:${PORT} file_server /uploads/* { root ${APP_DIR} } } EOF systemctl reload caddy # 8. Register in admin DB mysql pawprint_admin -e "INSERT INTO tenants (name, slug, subdomain, db_name, port, admin_email, admin_name, status, trial_ends_at) VALUES ('${ORG_NAME}', '${SLUG}', '${SLUG}.pawprint.app', '${DB_NAME}', ${PORT}, '${ADMIN_EMAIL}', '${ADMIN_NAME}', 'active', DATE_ADD(NOW(), INTERVAL 14 DAY));" # 9. Send welcome email echo "Sending welcome email..." node scripts/send-welcome.js \ --email "${ADMIN_EMAIL}" \ --name "${ADMIN_NAME}" \ --orgName "${ORG_NAME}" \ --url "https://${SLUG}.pawprint.app" \ --password "${TEMP_PASSWORD}" echo "=== ${SLUG}.pawprint.app is LIVE ===" ``` ## What the Rescue Gets After ~60 seconds of provisioning: 1. **Live site** at `{slug}.pawprint.app` with SSL 2. **Admin login** with temp password (forced change on first login) 3. **Welcome email** with login URL, getting started guide 4. **Empty but functional** — ready to add pets, customize content, upload photos 5. **14-day free trial** — full features, no credit card required ## Setup Wizard (First Login) When the admin first logs in, they see a setup wizard instead of the dashboard: ### Step 1: Organization Info - Logo upload - Organization name (pre-filled from signup) - Tagline / mission statement - Location (city, state) - Contact email, phone - Social links (Facebook, Instagram) ### Step 2: Branding - Primary color (default: teal) - Accent color - Font preference (2-3 options) - Preview of how the site looks ### Step 3: Application Forms - Toggle which application types to enable (adoption, foster, volunteer, surrender) - Customize adoption fee default - Edit agreement items (pre-populated with sensible defaults) ### Step 4: Integrations (Optional) - Stripe keys (for accepting payments) - Email SMTP (or use PawPrint's shared sender) - Facebook page connection - Petfinder widget (org ID) ### Step 5: Import Data (Optional) - Upload CSV of existing pets - Map columns to PawPrint fields - Preview and confirm import ### Done! - Redirect to dashboard - Checklist of "next steps" (add first pet, upload logo, share your site) ## Deprovisioning When a tenant cancels or trial expires: ```bash #!/bin/bash # deprovision-tenant.sh SLUG=$1 # 1. Stop service systemctl stop pawprint-${SLUG} systemctl disable pawprint-${SLUG} rm /etc/systemd/system/pawprint-${SLUG}.service # 2. Remove Caddy route # (sed out the block from tenants.caddy) systemctl reload caddy # 3. Archive data (keep for 30 days) mysqldump pp_${SLUG} > /backups/tenants/${SLUG}-$(date +%Y%m%d).sql tar -czf /backups/tenants/${SLUG}-uploads-$(date +%Y%m%d).tar.gz /var/www/pawprint/${SLUG}/uploads/ # 4. Mark as deleted in admin DB mysql pawprint_admin -e "UPDATE tenants SET status = 'deleted' WHERE slug = '${SLUG}';" # 5. Schedule hard delete after 30 days at now + 30 days << EOF mysql -e "DROP DATABASE IF EXISTS pp_${SLUG};" mysql -e "DROP USER IF EXISTS 'pp_${SLUG}'@'localhost';" rm -rf /var/www/pawprint/${SLUG} rm /backups/tenants/${SLUG}-*.sql rm /backups/tenants/${SLUG}-*.tar.gz EOF echo "=== ${SLUG} deprovisioned, data retained for 30 days ===" ``` ## Scaling Considerations ### 0-50 Tenants - Single VPS ($15-20/mo) - Everything on one box - Manual monitoring ### 50-200 Tenants - Separate DB server - Shared build (symlink, not copy) - Automated health checks - Consider container-per-tenant (Docker) ### 200+ Tenants - Multiple app servers - Managed database (PlanetScale, Aiven) - Cloudflare R2 for images - Kubernetes or Docker Swarm - Dedicated ops/monitoring (Grafana, alerts) ### 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 |