User Manual · 06 · Managing Clients
WholeReach — Automated Marketing System

Managing Clients — Selling It as a Service

How the same engine manages other people's social media: multi-tenant setup, onboarding, the approval portal, pricing, and white-label.

Yes — this manages other people's social media today.The engine is multi-tenant by design. Each client is one entry in a config file, with their own connected accounts, cadence, content, and (optionally) an approval step. Our own network is simply the first tenant.

The multi-tenant model

Every client — including us — is a "tenant". A tenant is defined by a single block in /opt/portal/clients.json:

"acme-builders": {
  "name": "Acme Builders",
  "brand": { "brand_name": "Acme", "tagline": "Built for life." },
  "cadence": 5,                    # posts kept in flight
  "channels": ["bluesky","linkedin"],  # their connected networks
  "require_approval": true,        # review before posting
  "template_module": "portal_templates_wt",
  "listings": "/opt/portal/listings/acme-builders.json"
}
Golden ruleNever point a demo/test tenant at a real client's channels. Test tenants use the "dry" channel, which logs a post without sending it anywhere.

Client onboarding — step by step

Add a new client, start to finish

In plain EnglishA client is one entry in a config file plus a file of their verified facts. This walkthrough takes a fictional client “Acme Builders” from nothing to live, safely (a dry run first, then real posting).

Do it — step by step

  1. Discovery. Agree what they want posted, how often, on which networks, and whether they want to approve each post or run hands-off.
  2. Connect their accounts. Have them authorize their networks through the hub — follow Playbook 1 once per network. Their tokens are stored under their own account.
  3. Create their facts file on the hub — the verified things their posts can draw from (products, listings, services, wins). Model it on an existing one:
    ssh root@64.227.29.192 nano /opt/portal/listings/acme-builders.json
  4. Add their tenant block to the config:
    ssh root@64.227.29.192 nano /opt/portal/clients.json
    Add an entry like:
    "acme-builders": {
      "name": "Acme Builders",
      "brand": { "brand_name": "Acme", "tagline": "Built for life." },
      "cadence": 5,
      "channels": ["dry"],            # dry first — see step 5
      "require_approval": true,
      "template_module": "portal_templates_wt",
      "listings": "/opt/portal/listings/acme-builders.json"
    }
  5. Dry run. With channels set to ["dry"], generate and review the drafts — nothing is sent anywhere:
    ssh root@64.227.29.192 python3 /opt/portal/generate.py
    Then read them with Playbook 9.
  6. Go live. When the drafts look right, change "channels" to their real networks (e.g. ["bluesky","linkedin"]) and regenerate. With require_approval: true, posts now wait in their queue.
  7. Give them their approval link (next section) — or approve on their behalf.

Under the hood (for the technical reader)

Always dry-run first. The ["dry"] channel logs a post without sending it — the safe way to prove a new client's output before anything reaches their real audience.

The approval portal

For clients who want a say, each tenant has a private, phone-friendly approval queue. The client opens a secure magic link — no password — and taps Approve, Edit, or Skip on each drafted post. Only approved posts publish.

  • Links are signed and time-limited (they expire).
  • Skips can capture a reason, which feeds back into better drafting.
  • Held posts (missing a verified fact) show up clearly and cannot be published until resolved.

This is the screen that sells the service: the client sees a tidy stream of on-brand posts and approves a week in two minutes.

In plain EnglishEach client gets their own private, expiring link — no login. You generate it once and send it to them.

Do it — step by step

  1. Generate a 30-day link for the client (use their tenant name):
    ssh root@64.227.29.192 "python3 -c \"import sys;sys.path.insert(0,'/opt/portal');import portal_db as db;print('https://postiz.wholetech.com/portal/app/?c=acme-builders&t='+db.make_token('acme-builders',30))\""
  2. It prints the full link. Send it to the client (or open it yourself to approve for them).
  3. When it expires, run the command again for a fresh one.

Under the hood (for the technical reader)

Tokens are HMAC-signed with the server secret and carry an expiry — they can't be forged or reused after they lapse.

Brand safety for client accounts

  • The honesty guard (see Content Engine) means the system never states an unverified fact about a client's business.
  • Approval mode puts a human in the loop for sensitive accounts.
  • The kill switch and per-tenant channels let you stop or narrow any client instantly.
  • Account isolation — each tenant's tokens and content are separate; one client can never post to another's accounts.

Pricing the service

Because generation is free and the only variable cost is X (pennies), margins are strong. Indicative managed-service tiers:

TierForIndicative price*Includes
StarterSolo / small business$300–500/mo1–2 networks, ~1 post/day, monthly report
ManagedGrowing brands / builders$500–1,500/mo3–4 networks, approval queue, weekly report, content tuning
SpecialistTechnical / niche verticals$1,000–2,500/moCustom content families, white-label, deeper reporting

*Estimates for planning, not a rate card. Set final pricing per engagement.

White-label

The approval portal and reports can be served under a partner's branding. Client-facing pages carry the agency's name; the WholeTech engine stays behind the scenes. This is how a partner (for example, an agency serving home builders) resells the system as their own product.