Docs / Trial approval flowSDK downloadsGitHub ↗

PushHub Trial Approval Flow

Purpose

PushHub needs an approval-based onboarding path for companies. Public users should request a free trial from the marketing website. Keme super-admins should review the request, provision the company after approval, and send the requester a secure CTA to set the workspace password.

This protects the workspace from unapproved tenant creation, gives Keme visibility into demand, and keeps provisioning/audit trails inside the admin control plane.

Flow

sequenceDiagram
    participant Visitor
    participant Marketing as Marketing website
    participant API as PushHub API
    participant Admin as Keme Admin
    participant Email as Email service
    participant Workspace as Company workspace

    Visitor->>Marketing: Click Request free trial
    Marketing->>API: POST /api/v1/trial-requests
    API-->>Visitor: Request received
    Admin->>API: GET /api/v1/admin/trial-requests
    Admin->>API: POST /api/v1/admin/trial-requests/{id}/approve
    API->>API: Provision company, owner, membership, setup token
    API->>Email: Send setup-password CTA
    API->>API: Record setup-email delivery status
    Email-->>Visitor: Set workspace password
    Admin->>API: Optional POST /admin/trial-requests/{id}/setup-link
    Visitor->>Workspace: Open /setup-password?token=...
    Workspace->>API: Validate token and set password
    API-->>Workspace: Login token / activated account

Implemented API contracts

Public trial request

POST /api/v1/trial-requests

Request body:

{
  "companyName": "My Game Studio",
  "fullName": "Studio Owner",
  "email": "owner@example.com",
  "website": "https://example.com",
  "useCase": "Player lifecycle push campaigns",
  "expectedMonthlyNotifications": 50000,
  "requestedPlanSlug": "free"
}

Response should return a non-secret request summary:

{
  "id": "uuid",
  "status": "Submitted",
  "email": "owner@example.com",
  "createdAt": "2026-07-17T00:00:00Z"
}

Admin review

All admin review endpoints use the existing super-admin route and policy:

  • GET /api/v1/admin/trial-requests?status=Submitted&page=1&pageSize=20
  • GET /api/v1/admin/trial-requests/{id}
  • POST /api/v1/admin/trial-requests/{id}/approve
  • POST /api/v1/admin/trial-requests/{id}/reject
  • POST /api/v1/admin/trial-requests/{id}/resend-setup-email
  • POST /api/v1/admin/trial-requests/{id}/setup-link

Approval should accept review/provisioning options:

{
  "planSlug": "free",
  "trialDays": 14,
  "adminNotes": "Approved for launch trial"
}

Rejection should accept a clear reason:

{
  "adminNotes": "Not a fit for the private beta yet. Follow up next quarter."
}

Setup password

The frontend CTA should use:

https://app.pushhub.kemegames.com/setup-password?token={rawToken}

The raw token must only be sent by email or returned in safe local/test log modes. It must never be stored unhashed.

Implemented setup endpoints:

  • GET /api/v1/setup-password/{token}
  • POST /api/v1/setup-password

Token-info response should return only non-secret context:

{
  "email": "owner@example.com",
  "fullName": "Studio Owner",
  "companyName": "My Game Studio",
  "expiresAt": "2026-07-24T00:00:00Z",
  "isValid": true
}

Password setup request:

{
  "token": "raw-token-from-email",
  "password": "new-password"
}

Admin website requirements

The Keme Admin website includes a Trial Requests module at /admin/trial-requests with:

  • submitted/approved/rejected/expired filters
  • requester/company/search filters
  • approve action with plan and trial length
  • reject action with reason/notes
  • inline request detail in the table and review modal
  • audit-log creation for submit, approve, reject, and password setup

Email delivery uses the configured transactional provider. Resend is preferred when RESEND_API_KEY is configured; SMTP remains available as a fallback provider. Without a configured provider, or if setup email delivery fails, the service writes a fallback message to logs for operational recovery.

Approved requests include a Keme Admin Resend setup action. Because raw setup tokens are never stored, this action creates a fresh setup-password token, invalidates existing open setup tokens for the owner, and sends the new CTA through the configured email provider.

Approved requests also include a Keme Admin Setup link recovery action. This action is super-admin-only, creates a fresh seven-day setup token, invalidates older open setup tokens for the owner, audits trial_request.setup_link_generated, and returns the raw setup URL only in that one response. The normal list/detail response never exposes raw setup tokens.

The admin table shows setup-email delivery state:

  • NotSent: no setup email attempt has been recorded yet.
  • Sent: Resend or SMTP accepted the setup email.
  • Fallback: email was not delivered, but the setup CTA was written to API logs for operational recovery.
  • Failed: an unexpected email-service failure occurred before a fallback could be completed.

Each request also records setup-email attempt count, last attempt time, provider, provider message id when available, last error, and the latest manual link generation time.

Marketing and workspace requirements

  • Marketing Start free and Request free trial CTAs should route to /request-trial.
  • /request-trial should not ask for a password.
  • /register redirects to /request-trial.
  • /setup-password validates the token before showing password fields.
  • After password setup, the owner is logged into the workspace and redirected to /dashboard.
  • The trial duration selected during admin approval is applied when password setup completes. If no approved duration exists, the backend falls back to 14 days.

Email requirements

Required templates:

  • Trial request received
  • Trial approved / set password
  • Trial rejected
  • Welcome after password setup

Production email delivery prefers Resend when RESEND_API_KEY is configured, then SMTP when Email:Smtp:* is configured, then log fallback. Resend production delivery uses RESEND_API_KEY, RESEND_API_URL, EMAIL_FROM_EMAIL, and EMAIL_FROM_NAME. See docs/EMAIL_DELIVERY.md.

Security requirements

  • Store only setup-token hashes.
  • Expire setup tokens, recommended default: 7 days.
  • Mark tokens used after successful password setup.
  • Do not log raw setup tokens in production.
  • Do not allow workspace access before approval and password setup.
  • Enforce company status at login and protected workspace endpoints.
  • Audit trial request submission, approval, rejection, provisioning, email send result, and password setup.

Production CORS requirements

The trial-request form is served from the public marketing host, while the API runs on api.pushhub.kemegames.com. Production must allow all first-party PushHub web origins at both layers:

  • API application CORS: https://pushhub.kemegames.com, https://www.pushhub.kemegames.com, https://app.pushhub.kemegames.com, and https://admin.pushhub.kemegames.com.
  • Public Nginx CORS: reflect only the same allow-listed origins and return 204 for OPTIONS preflight requests under /api/. The one exception is the client SDK routes (/api/v1/sdk/*), which allow any origin without credentials (see docs/DIRECT_VPS_DEPLOYMENT.md).
  • Public Nginx should hide backend Access-Control-Allow-* headers from proxied responses so browsers receive one clean CORS policy instead of duplicate Access-Control-Allow-Origin values.

If the proxy hardcodes only the app origin, browsers on the marketing site will block trial-request responses and show a generic submission failure even when the backend handled the request correctly.

Verification

Run the following before deployment:

npm --prefix frontend run build
docker build -f backend/PushHub.API/Dockerfile -t ghcr.io/kemegames-studio/pushhub-api:workspace-20260706 backend

Run the full smoke script after deployment:

API_BASE=https://api.pushhub.kemegames.com/api/v1 \
APP_BASE=https://app.pushhub.kemegames.com \
PUSHHUB_DIR=/opt/pushhub \
scripts/verify-trial-flow.sh

Production deployment on July 17, 2026 verified:

  • GET https://api.pushhub.kemegames.com/api/v1/health returns 200.
  • GET https://api.pushhub.kemegames.com/api/v1/setup-password/smoke returns 404, not 500.
  • GET https://api.pushhub.kemegames.com/api/v1/admin/trial-requests returns 401 without a super-admin token.
  • OPTIONS https://api.pushhub.kemegames.com/api/v1/trial-requests reflects the caller origin for the marketing, workspace, and admin hosts.
  • Duplicate POST /api/v1/trial-requests responses return controlled 409 Conflict with a single Access-Control-Allow-Origin header for the caller origin.
  • https://app.pushhub.kemegames.com/request-trial, /setup-password?token=smoke, and /admin/trial-requests return the SPA with 200.
  • A live smoke request was submitted, found in the Keme admin inbox, and rejected immediately so no fake tenant was provisioned.
  • A full live E2E smoke on July 17, 2026 created a trial request, approved it from Keme Admin, validated the setup-password CTA from the email fallback log, set the owner password, verified workspace login, and suspended the smoke-test company afterwards.

Production end-to-end verification on September 24, 2026 used the isolated test company Keme Flow Test 20260924210362 (trial request de9f4bfd-b35a-4e47-9bee-e4995cd4aa60):

  • The marketing homepage opened and its primary trial CTA led to the public request form.
  • The request form displayed Request received; the production database recorded the matching request as Submitted before review.
  • A Keme administrator approved that request in the admin panel. The database recorded Approved, provisioned company e6e96f84-18d4-4d94-9821-db43825f28e3, and one setup-email attempt with status Sent and provider Resend.
  • Resend message 01a0d51a-1395-772a-b18c-235dfd3a1b85 reported last_event: delivered for the test alias. This proves provider delivery, but the recipient's Gmail inbox was not independently inspected.
  • The exact CTA URL in the sent message opened a valid setup form naming the approved company and owner. A generated test password activated the workspace and opened the owner dashboard.
  • After signing out, a fresh password sign-in returned to the same company dashboard. Reopening the original setup URL showed This setup link is not available, confirming single-use behavior.
  • No provider connection, app, device, or campaign was created. The test company remains provisioned as an isolated verification record; the generated password and setup token are not stored in this document.