End-to-End Process Flow
From partner sign-up to live branded portal — 8 steps, fully scripted
Partner Submits Form
Popup collects domain, brand, logo, color, tier
Provision Database
New schema in DigitalOcean Postgres + all tables replicated
Provision Storage
Dedicated R2 bucket in Cloudflare with partner prefix
DNS Subdomain
Cloudflare API creates subdomain → CNAME to app
Apply Theme
CSS stylesheet assigned + logo stored in R2
Seed Super Admin
Admin credentials created, welcome email via Resend
Register in Master
Tenant row added to super_admin.whitelabel_tenants
Go Live ✓
Partner receives login link + credentials via Resend
Technical Replication Matrix
What gets duplicated per white-label tenant
Isolated DB Schema
- New schema:
tenant_{slug} - Tables: users, clients, documents, messages, esign_envelopes, audit_logs
- Row-level security via schema isolation
- Connection pool entry added
Dedicated Storage Bucket
- Bucket:
gettaxwise-{slug} - Prefixes: /documents/, /logos/, /esign/
- CORS policy applied
- Access keys scoped to this bucket
Subdomain Provisioning
- CNAME:
{slug}.gettaxwise.com - Proxied through Cloudflare
- SSL auto-provisioned
- Custom domain option (CNAME hand-off)
Tenant Auth Config
- Tenant ID stored in DB config
- SMS + email 2FA rules cloned
- Custom email template per brand
eSign Templates
- Template set duplicated per tenant
- Branded header/footer applied
- Webhook endpoint registered
Email Identity
- Domain:
noreply@{slug}.gettaxwise.com - Welcome + notification templates
- DNS records auto-added
Subscription Tiers
Client capacity limits enforced at the database & application layer
Tier 1
Starter
- Up to 10 sub-clients
- 1 admin user
- 5 GB R2 storage
- Standard email templates
- Subdomain included
Tier 2
Growth
- 11–20 sub-clients
- 3 admin users
- 20 GB R2 storage
- Custom email domain
- Priority support
Tier 3
Scale
- 21–40 sub-clients
- Unlimited admins
- 100 GB R2 storage
- Custom domain CNAME
- Dedicated onboarding
Replication Scripts
Dummy Python — drop-in architecture ready for your environment variables
# GetTaxWise White-Label Provisioning — Master Script # Run: python provision_tenant.py --config tenant_config.json import json, argparse, logging from modules import ( provision_database, provision_storage, provision_dns, provision_email, provision_auth, provision_esign, register_tenant, send_welcome_email, ) def main(): parser = argparse.ArgumentParser() parser.add_argument("--config", required=True) args = parser.parse_args() with open(args.config) as f: config = json.load(f) slug = config["slug"] # e.g. "smithcpa" logging.info(f"Provisioning tenant: {slug}") steps = [ ("Database schema", provision_database, config), ("R2 storage bucket", provision_storage, config), ("DNS subdomain", provision_dns, config), ("Email identity", provision_email, config), ("Firebase auth", provision_auth, config), ("Docuseal eSign", provision_esign, config), ("Master registry", register_tenant, config), ("Welcome email", send_welcome_email, config), ] results = {} for label, fn, cfg in steps: try: result = fn(cfg) results[label] = {"status": "ok", "data": result} logging.info(f" ✓ {label}") except Exception as e: results[label] = {"status": "error", "error": str(e)} logging.error(f" ✗ {label}: {e}") break # Halt on failure — no partial tenants with open(f"results_{slug}.json", "w") as f: json.dump(results, f, indent=2) if __name__ == "__main__": main()
# Creates an isolated schema + all tables for a new tenant import psycopg2 from config import DB_URL TABLES = [ """CREATE TABLE {schema}.users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT UNIQUE NOT NULL, role TEXT DEFAULT 'client', is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now() )""", """CREATE TABLE {schema}.documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES {schema}.users(id), file_name TEXT NOT NULL, r2_key TEXT NOT NULL, uploaded_at TIMESTAMPTZ DEFAULT now() )""", """CREATE TABLE {schema}.messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), from_user UUID REFERENCES {schema}.users(id), to_user UUID REFERENCES {schema}.users(id), body TEXT, sent_at TIMESTAMPTZ DEFAULT now() )""", """CREATE TABLE {schema}.esign_envelopes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES {schema}.users(id), docuseal_id TEXT, status TEXT DEFAULT 'pending', created_at TIMESTAMPTZ DEFAULT now() )""", """CREATE TABLE {schema}.audit_logs ( id SERIAL PRIMARY KEY, user_id UUID, action TEXT, metadata JSONB, ts TIMESTAMPTZ DEFAULT now() )""", ] def provision_database(config): schema = f"tenant_{config['slug']}" conn = psycopg2.connect(DB_URL) conn.autocommit = True cur = conn.cursor() # 1. Create isolated schema cur.execute(f"CREATE SCHEMA IF NOT EXISTS {schema}") # 2. Create all tables for tpl in TABLES: cur.execute(tpl.format(schema=schema)) # 3. Register in super-admin tenant registry cur.execute(""" INSERT INTO super_admin.whitelabel_tenants (slug, company_name, schema_name, tier, color_theme, domain, created_at) VALUES (%s, %s, %s, %s, %s, %s, now()) """, ( config["slug"], config["company_name"], schema, config["tier"], config["color_theme"], config["domain"] )) cur.close(); conn.close() return {"schema": schema}
# Creates a dedicated R2 bucket + sets CORS policy per tenant import boto3 from config import R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY def provision_storage(config): slug = config["slug"] bucket_name = f"gettaxwise-{slug}" s3 = boto3.client( "s3", endpoint_url=R2_ENDPOINT, aws_access_key_id=R2_ACCESS_KEY, aws_secret_access_key=R2_SECRET_KEY, ) # Create bucket s3.create_bucket(Bucket=bucket_name) # Set CORS for portal access s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ "CORSRules": [{ "AllowedOrigins": [f"https://{slug}.gettaxwise.com"], "AllowedMethods": ["GET", "PUT", "DELETE"], "AllowedHeaders": ["*"], }] }) # Create standard prefixes (zero-byte objects) for prefix in ["documents/", "logos/", "esign/"]: s3.put_object(Bucket=bucket_name, Key=prefix, Body=b"") return {"bucket": bucket_name}
# Creates CNAME subdomain via Cloudflare API import requests from config import CF_API_TOKEN, CF_ZONE_ID, APP_CNAME_TARGET def provision_dns(config): slug = config["slug"] subdomain = f"{slug}.gettaxwise.com" resp = requests.post( f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records", headers={"Authorization": f"Bearer {CF_API_TOKEN}"}, json={ "type": "CNAME", "name": subdomain, "content": APP_CNAME_TARGET, "proxied": True, # Cloudflare proxy = free SSL "ttl": 1, } ) resp.raise_for_status() return {"subdomain": subdomain, "record_id": resp.json()["result"]["id"]}
# Registers sending domain in Resend + adds DNS records import resend from config import RESEND_API_KEY def provision_email(config): slug = config["slug"] resend.api_key = RESEND_API_KEY # Add domain to Resend domain = resend.Domains.create({"name": f"{slug}.gettaxwise.com"}) # Auto-add Resend's DNS records to Cloudflare (helper from provision_dns) from modules.provision_dns import add_dns_record for record in domain["records"]: add_dns_record(record) # SPF, DKIM, DMARC return {"from_address": f"noreply@{slug}.gettaxwise.com"}
{
"slug": "smithcpa",
"company_name": "Smith & Associates CPA",
"app_name": "SmithTax Portal",
"domain": "smithcpa.gettaxwise.com",
"admin_email": "admin@smithcpa.com",
"color_theme": "ocean",
"logo_r2_key": "logos/smithcpa_logo.png",
"tier": "tier2",
"max_clients": 20
}
Go-Live Validation Checklist
Reviewed from the white-label client's perspective — what they verify on day one