GetTaxWise White-Label

End-to-End Process Flow

From partner sign-up to live branded portal — 8 steps, fully scripted

Step 01

Partner Submits Form

Popup collects domain, brand, logo, color, tier

Step 02

Provision Database

New schema in DigitalOcean Postgres + all tables replicated

Step 03

Provision Storage

Dedicated R2 bucket in Cloudflare with partner prefix

Step 04

DNS Subdomain

Cloudflare API creates subdomain → CNAME to app

Step 05

Apply Theme

CSS stylesheet assigned + logo stored in R2

Step 06

Seed Super Admin

Admin credentials created, welcome email via Resend

Step 07

Register in Master

Tenant row added to super_admin.whitelabel_tenants

Step 08

Go Live ✓

Partner receives login link + credentials via Resend

Technical Replication Matrix

What gets duplicated per white-label tenant

DigitalOcean Postgres

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
Cloudflare R2

Dedicated Storage Bucket

  • Bucket: gettaxwise-{slug}
  • Prefixes: /documents/, /logos/, /esign/
  • CORS policy applied
  • Access keys scoped to this bucket
Cloudflare DNS

Subdomain Provisioning

  • CNAME: {slug}.gettaxwise.com
  • Proxied through Cloudflare
  • SSL auto-provisioned
  • Custom domain option (CNAME hand-off)
Firebase Auth

Tenant Auth Config

  • Tenant ID stored in DB config
  • SMS + email 2FA rules cloned
  • Custom email template per brand
Docuseal

eSign Templates

  • Template set duplicated per tenant
  • Branded header/footer applied
  • Webhook endpoint registered
Resend

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 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

provision_tenant.py — Master orchestration script
# 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()
modules/provision_database.py — Postgres schema isolation
# 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}
modules/provision_storage.py — Cloudflare R2 bucket
# 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}
modules/provision_dns.py — Cloudflare subdomain
# 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"]}
modules/provision_email.py — Resend domain identity
# 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"}
tenant_config.json — Input config generated by the onboarding popup
{
  "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

Subdomain resolves https://{slug}.gettaxwise.com loads, SSL green, branded name/logo visible
Login + 2FA works Firebase SMS and email verification fire correctly for this tenant's admin account
Color theme applied Correct stylesheet loaded — no bleed from other tenants' themes
Client invite flow Add a test client → invite email arrives from noreply@{slug}.gettaxwise.com
Document upload → R2 File stored in tenant's dedicated bucket, not shared storage
eSign envelope sends Docuseal envelope created with branded header, webhook fires back to correct schema
Messaging portal Admin ↔ client messages stored in tenant_{slug}.messages — no cross-tenant access
!
Tier cap enforced Attempt to add client #(max+1) — system blocks with upgrade prompt
Super admin visibility Master dashboard shows new tenant row with correct tier, usage metrics
Data isolation test Log in as tenant A — cannot query or see tenant B's data in any request