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:
| Action | Description |
|---|---|
ai_generation | AI-powered email generation (Claude) |
email_send | Sending 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:
| Course | Modules | Module IDs |
|---|---|---|
| Developer | 6 | introduction, checkbox-hack, lightswitch, tabbed-elements, engagement-quiz, tracking |
| Marketing | 5 | marketing-why-kinetic, marketing-ecommerce, marketing-subscriptions, marketing-newsletters, marketing-education |
| Coding 101 | 5 | coding-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:
- On page load, checks if any course is fully complete
- Calls
award_course_completion_tokens()RPC - 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_codestable
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 >= costto 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:
- Calls
checkCanAfford()for instant UI feedback (no deduction) - If affordable, executes the action (server deducts tokens)
- 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();