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=20GET /api/v1/admin/trial-requests/{id}POST /api/v1/admin/trial-requests/{id}/approvePOST /api/v1/admin/trial-requests/{id}/rejectPOST /api/v1/admin/trial-requests/{id}/resend-setup-emailPOST /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 freeandRequest free trialCTAs should route to/request-trial. /request-trialshould not ask for a password./registerredirects to/request-trial./setup-passwordvalidates 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, andhttps://admin.pushhub.kemegames.com. - Public Nginx CORS: reflect only the same allow-listed origins and return
204forOPTIONSpreflight requests under/api/. - Public Nginx should hide backend
Access-Control-Allow-*headers from proxied responses so browsers receive one clean CORS policy instead of duplicateAccess-Control-Allow-Originvalues.
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/healthreturns200.GET https://api.pushhub.kemegames.com/api/v1/setup-password/smokereturns404, not500.GET https://api.pushhub.kemegames.com/api/v1/admin/trial-requestsreturns401without a super-admin token.OPTIONS https://api.pushhub.kemegames.com/api/v1/trial-requestsreflects the caller origin for the marketing, workspace, and admin hosts.- Duplicate
POST /api/v1/trial-requestsresponses return controlled409 Conflictwith a singleAccess-Control-Allow-Originheader for the caller origin. https://app.pushhub.kemegames.com/request-trial,/setup-password?token=smoke, and/admin/trial-requestsreturn the SPA with200.- 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.