Skip to main content

Token Economy

Kinetic Email uses a token-based economy to gate premium actions like AI generation and email sending. Tokens are the internal currency — users earn them through course completion and referrals, and spend them on AI-powered features.

How It Works

User earns tokens (course completion, referrals, admin grants)


Token balance stored in Supabase (token_balances table)


User triggers premium action (e.g., AI Playground)

├── Frontend: useTokenGate() pre-checks balance (read-only)
│ └── If insufficient → blocks action with UI feedback

└── Backend: spendTokensServer() deducts tokens (atomic)
└── If insufficient → returns 402

Token Actions & Costs

Action costs are defined in the token_action_costs table:

ActionDescription
ai_generationAI-powered email generation (Claude)
email_sendSending a test email via Resend/SES

Costs are configurable per action — adjust them in the database without code changes.

Earning Tokens

Course Completion

Completing all modules in a course awards bonus tokens:

CourseModulesModule IDs
Developer6introduction, checkbox-hack, lightswitch, tabbed-elements, engagement-quiz, tracking
Marketing5marketing-why-kinetic, marketing-ecommerce, marketing-subscriptions, marketing-newsletters, marketing-education
Coding 1015coding-html-basics, coding-css-basics, coding-why-tables, coding-mso-conditionals, coding-div-future

The bonus amount is stored in token_config table (course_completion_bonus key).

Auto-award logic in LearningProgressContext:

  1. On page load, checks if any course is fully complete
  2. Calls award_course_completion_tokens() RPC
  3. SQL function prevents double-awarding (idempotent)

Referrals

Each user gets a unique referral code. When a new user signs up with ?ref=CODE:

  • The process_referral() RPC awards tokens to the referrer
  • Referral codes are stored in the referral_codes table

Database Schema

token_balances

user_id UUID PRIMARY KEY REFERENCES auth.users(id)
balance INTEGER DEFAULT 0
lifetime_earned INTEGER DEFAULT 0
lifetime_spent INTEGER DEFAULT 0

token_transactions

id UUID PRIMARY KEY
user_id UUID REFERENCES auth.users(id)
amount INTEGER -- positive = credit, negative = debit
balance_after INTEGER
transaction_type TEXT -- 'course_completion', 'referral', 'spend', 'admin_grant'
description TEXT
reference_id TEXT -- e.g., course ID, action name
created_at TIMESTAMPTZ

token_action_costs

action_name TEXT PRIMARY KEY
cost INTEGER
description TEXT

Server-Side Operations

The token system uses PostgreSQL SECURITY DEFINER functions for all sensitive operations:

  • Token spending — Atomic deduction using UPDATE ... WHERE balance >= cost to prevent negative balances and race conditions
  • Balance checking — Read-only affordability check for instant frontend UX feedback (no deduction)
  • Course awards — Idempotent bonus awards that prevent double-granting

All functions are scoped to the calling user via auth.uid() and are invoked through the Supabase client SDK.

Frontend Integration

useTokenGate() Hook

const { executeWithTokens, isBlocked, errorMessage } = useTokenGate();

// Pre-checks balance, executes action, refreshes balance after
const result = await executeWithTokens('ai_generation', async () => {
return await fetch('/api/generate', { ... });
});

if (result === null) {
// Blocked — insufficient tokens
}

The hook:

  1. Calls checkCanAfford() for instant UI feedback (no deduction)
  2. If affordable, executes the action (server deducts tokens)
  3. Calls refreshBalance() to sync the UI with the server-side deduction

TokenContext

const {
balance, // Current token balance
isLoading, // Balance loading state
spendTokens, // Direct RPC call (used by backend)
checkCanAfford, // Read-only balance check
refreshBalance, // Refresh balance from server
awardCourseCompletion, // Award course completion bonus
getReferralUrl, // Get user's referral link
} = useTokens();