[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Waitlist with Referral Boost

Build a Waitlist with Referral Boost

Build a pre-launch waitlist with referral-based queue jumping, position tracking, milestone rewards, early access drip, and analytics — turning signups into a viral growth engine.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Kai is launching a new developer tool. They need to build hype pre-launch, collect 10K signups, and prioritize the most engaged users for early access. A simple email signup form gets names but doesn't create urgency or sharing. They saw how Robinhood got 1M waitlist signups with a referral system — your position moves up when friends join. They need a waitlist that incentivizes sharing, shows position, and lets them drip early access to top referrers first.

Step 1: Build the Waitlist Engine

typescript
// src/waitlist/engine.ts — Viral waitlist with referral boost and milestone rewards
import { randomBytes } from "node:crypto";
import { pool } from "../db";
import { Redis } from "ioredis";

const redis = new Redis(process.env.REDIS_URL!);

const REFERRAL_BOOST = 5;          // each referral moves you up 5 positions
const ACCESS_BATCH_SIZE = 100;     // invite 100 users at a time

interface WaitlistEntry {
  id: string;
  email: string;
  referralCode: string;
  referredBy: string | null;
  referralCount: number;
  position: number;
  score: number;               // base position - referral boosts
  status: "waiting" | "invited" | "activated";
  milestones: string[];
  joinedAt: string;
}

interface WaitlistStats {
  totalSignups: number;
  waitingCount: number;
  invitedCount: number;
  activatedCount: number;
  topReferrers: Array<{ email: string; referralCount: number }>;
  signupsToday: number;
  conversionRate: number;
}

// Join the waitlist
export async function joinWaitlist(
  email: string,
  referralCode?: string
): Promise<{
  entry: WaitlistEntry;
  position: number;
  totalAhead: number;
  referralLink: string;
}> {
  // Check if already registered
  const { rows: existing } = await pool.query(
    "SELECT * FROM waitlist WHERE email = $1",
    [email.toLowerCase()]
  );
  if (existing.length > 0) {
    throw new Error("This email is already on the waitlist");
  }

  // Generate unique referral code
  const myReferralCode = randomBytes(4).toString("hex");

  // Get current position (end of line)
  const { rows: [{ count }] } = await pool.query("SELECT COUNT(*) as count FROM waitlist");
  const position = parseInt(count) + 1;

  // Look up referrer
  let referredById: string | null = null;
  if (referralCode) {
    const { rows: [referrer] } = await pool.query(
      "SELECT id FROM waitlist WHERE referral_code = $1",
      [referralCode]
    );
    if (referrer) {
      referredById = referrer.id;

      // Boost referrer's position
      await pool.query(
        `UPDATE waitlist SET
           referral_count = referral_count + 1,
           score = score - $2
         WHERE id = $1`,
        [referrer.id, REFERRAL_BOOST]
      );

      // Check milestones for referrer
      await checkMilestones(referrer.id);
    }
  }

  const id = `wl-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;

  await pool.query(
    `INSERT INTO waitlist (id, email, referral_code, referred_by, referral_count, position, score, status, joined_at)
     VALUES ($1, $2, $3, $4, 0, $5, $5, 'waiting', NOW())`,
    [id, email.toLowerCase(), myReferralCode, referredById, position]
  );

  // Update daily signup counter
  await redis.incr(`waitlist:signups:${new Date().toISOString().slice(0, 10)}`);
  await redis.expire(`waitlist:signups:${new Date().toISOString().slice(0, 10)}`, 86400 * 30);

  const referralLink = `${process.env.APP_URL}/waitlist?ref=${myReferralCode}`;

  // Send welcome email with referral link
  await redis.rpush("email:queue", JSON.stringify({
    type: "waitlist_welcome",
    to: email,
    position,
    referralLink,
    referralCode: myReferralCode,
  }));

  return {
    entry: {
      id, email, referralCode: myReferralCode, referredBy: referredById,
      referralCount: 0, position, score: position, status: "waiting",
      milestones: [], joinedAt: new Date().toISOString(),
    },
    position,
    totalAhead: position - 1,
    referralLink,
  };
}

// Get current position (recalculated based on score)
export async function getPosition(email: string): Promise<{
  position: number;
  totalAhead: number;
  totalWaiting: number;
  referralCount: number;
  referralLink: string;
  milestones: string[];
  estimatedAccessDate: string | null;
}> {
  const { rows: [entry] } = await pool.query(
    "SELECT * FROM waitlist WHERE email = $1",
    [email.toLowerCase()]
  );
  if (!entry) throw new Error("Email not found on waitlist");

  // Position is rank by score (lower score = higher position)
  const { rows: [{ rank }] } = await pool.query(
    `SELECT COUNT(*) + 1 as rank FROM waitlist
     WHERE score < $1 AND status = 'waiting'`,
    [entry.score]
  );

  const { rows: [{ total }] } = await pool.query(
    "SELECT COUNT(*) as total FROM waitlist WHERE status = 'waiting'"
  );

  const position = parseInt(rank);
  const totalWaiting = parseInt(total);

  // Estimate access date based on current invite rate
  const batchesAhead = Math.ceil(position / ACCESS_BATCH_SIZE);
  const estimatedDays = batchesAhead * 3; // invite every 3 days
  const estimatedDate = new Date(Date.now() + estimatedDays * 86400000);

  return {
    position,
    totalAhead: position - 1,
    totalWaiting,
    referralCount: entry.referral_count,
    referralLink: `${process.env.APP_URL}/waitlist?ref=${entry.referral_code}`,
    milestones: entry.milestones || [],
    estimatedAccessDate: estimatedDate.toISOString().slice(0, 10),
  };
}

// Invite next batch of users
export async function inviteNextBatch(batchSize: number = ACCESS_BATCH_SIZE): Promise<{
  invitedCount: number;
  emails: string[];
}> {
  // Get top users by score (lowest score = highest priority)
  const { rows } = await pool.query(
    `UPDATE waitlist SET status = 'invited', invited_at = NOW()
     WHERE id IN (
       SELECT id FROM waitlist WHERE status = 'waiting'
       ORDER BY score ASC
       LIMIT $1
     )
     RETURNING email`,
    [batchSize]
  );

  // Send invitation emails
  for (const row of rows) {
    await redis.rpush("email:queue", JSON.stringify({
      type: "waitlist_invitation",
      to: row.email,
      activationUrl: `${process.env.APP_URL}/activate?email=${encodeURIComponent(row.email)}`,
    }));
  }

  return { invitedCount: rows.length, emails: rows.map((r) => r.email) };
}

// Referral milestones
async function checkMilestones(userId: string): Promise<void> {
  const { rows: [user] } = await pool.query(
    "SELECT referral_count, milestones, email FROM waitlist WHERE id = $1",
    [userId]
  );

  const milestones: Record<number, { name: string; reward: string }> = {
    3: { name: "early_bird", reward: "1 month free" },
    5: { name: "influencer", reward: "Priority access" },
    10: { name: "ambassador", reward: "Lifetime founder plan" },
    25: { name: "legendary", reward: "Lifetime + swag pack" },
  };

  const currentMilestones = user.milestones || [];

  for (const [threshold, milestone] of Object.entries(milestones)) {
    if (user.referral_count >= parseInt(threshold) && !currentMilestones.includes(milestone.name)) {
      currentMilestones.push(milestone.name);

      // Notify user of milestone
      await redis.rpush("email:queue", JSON.stringify({
        type: "waitlist_milestone",
        to: user.email,
        milestone: milestone.name,
        reward: milestone.reward,
        referralCount: user.referral_count,
      }));
    }
  }

  await pool.query("UPDATE waitlist SET milestones = $2 WHERE id = $1", [userId, JSON.stringify(currentMilestones)]);
}

// Analytics
export async function getStats(): Promise<WaitlistStats> {
  const { rows: [counts] } = await pool.query(`
    SELECT
      COUNT(*) as total,
      COUNT(*) FILTER (WHERE status = 'waiting') as waiting,
      COUNT(*) FILTER (WHERE status = 'invited') as invited,
      COUNT(*) FILTER (WHERE status = 'activated') as activated
    FROM waitlist
  `);

  const { rows: topReferrers } = await pool.query(
    "SELECT email, referral_count FROM waitlist WHERE referral_count > 0 ORDER BY referral_count DESC LIMIT 10"
  );

  const todayKey = `waitlist:signups:${new Date().toISOString().slice(0, 10)}`;
  const signupsToday = parseInt(await redis.get(todayKey) || "0");

  return {
    totalSignups: parseInt(counts.total),
    waitingCount: parseInt(counts.waiting),
    invitedCount: parseInt(counts.invited),
    activatedCount: parseInt(counts.activated),
    topReferrers,
    signupsToday,
    conversionRate: parseInt(counts.activated) / Math.max(parseInt(counts.invited), 1) * 100,
  };
}

Results

  • 10K signups in 3 weeks — referral incentive (position boost) motivated sharing; average user referred 2.3 friends; organic viral coefficient of 1.4
  • Top referrers became evangelists — milestone rewards (lifetime plan at 10 referrals) created power users who posted on Twitter, Reddit, and HN
  • Position anxiety drives engagement — users check their position daily; seeing "position moved from #3,400 to #1,200" after sharing creates dopamine loop
  • Early access to most engaged users — top referrers get invited first; these users are most likely to activate, give feedback, and spread word of mouth
  • Clean email list — users who sign up through referrals have 3x higher open rates; the waitlist pre-qualifies engaged users