Add Waitlist + Viral Referral to Any SaaS 2026
TL;DR
The fastest path to 1,000 waitlist signups is referral mechanics — reward users for bringing friends. A custom waitlist with position-based referrals can be added to any SaaS boilerplate in under a day. Three approaches: (1) embed Waitlisted.co or Loops for zero-code, (2) use the Prisma schema + Next.js API in this guide for a custom solution, (3) add ReferralHero for a full self-serve referral program. Each option takes different time and gives different control.
Key Takeaways
- Fastest (< 1 hour): embed Waitlisted.co or Loops — pre-built, no code
- Custom (2-4 hours): Prisma schema + API + email — full control, your branding
- Viral mechanic: share referral link → move up waitlist → reward early access
- Email capture is the goal: every waitlist subscriber is an owned email, not a rented social follower
- Referral boost math: position-based referrals typically 2-4x organic signups
- Convert waitlist → users: segment by referral count when opening early access
Why Waitlists Still Work in 2026
Waitlists are more effective than "coming soon" pages for three reasons:
- FOMO mechanics — "4,382 people are waiting" creates urgency
- Viral sharing — people share to move up the queue
- Email capture — you build your list before you build the product
The referral boost effect: early Loom and Superhuman both used position-based waitlists. The "share to jump the queue" mechanic reliably produces 3-5x the signups of a plain email capture form.
Option 1: Zero-Code — Waitlisted.co or Loops
Time: < 30 minutes Cost: Free tier available
The fastest path — embed a third-party widget:
<!-- Waitlisted.co embed (in your landing page): -->
<div id="waitlisted-widget" data-api-key="your_api_key"></div>
<script src="https://waitlisted.co/widget.js"></script>
<!-- Or Loops.so waitlist embed: -->
<script src="https://r.loops.so/embed.min.js" data-form-id="your-form-id"></script>
Waitlisted.co features (free):
- Referral tracking with custom share URL
- Position on waitlist shown to user
- Email notifications when position improves
- Dashboard with referral analytics
Loops.so approach: Loops is primarily an email tool but has waitlist forms — better if you plan to use Loops for all transactional emails anyway.
When to use third-party:
- Pre-launch (no codebase yet)
- Validating an idea (no time to build)
- < 5,000 expected signups (free tiers cover this)
Option 2: Custom Waitlist in Any Boilerplate
Time: 2-4 hours Cost: $0 (+ email service costs) Best for: when you want full control, custom branding, and data in your own DB
Database Schema
// prisma/schema.prisma — add to any boilerplate:
model WaitlistEntry {
id String @id @default(cuid())
email String @unique
name String?
position Int @unique // Position in queue
referralCode String @unique // Their unique referral link code
referredById String? // Who referred them
referredBy WaitlistEntry? @relation("Referrals", fields: [referredById], references: [id])
referrals WaitlistEntry[] @relation("Referrals")
referralCount Int @default(0) // Cached count
earlyAccess Boolean @default(false)
accessGrantedAt DateTime?
createdAt DateTime @default(now())
@@index([referralCode])
@@index([referredById])
}
API Route — Signup
// app/api/waitlist/route.ts
import { db } from '@/lib/db';
import { sendWelcomeEmail } from '@/lib/email';
import { nanoid } from 'nanoid';
export async function POST(req: Request) {
const { email, name, referralCode } = await req.json();
// Validate email:
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return Response.json({ error: 'Invalid email' }, { status: 400 });
}
// Check if already on waitlist:
const existing = await db.waitlistEntry.findUnique({ where: { email } });
if (existing) {
return Response.json({
message: 'Already on the waitlist!',
position: existing.position,
referralCode: existing.referralCode,
});
}
// Find referrer if code provided:
let referredById: string | undefined;
if (referralCode) {
const referrer = await db.waitlistEntry.findUnique({
where: { referralCode },
});
if (referrer) {
referredById = referrer.id;
}
}
// Get next position:
const count = await db.waitlistEntry.count();
// Create entry:
const entry = await db.waitlistEntry.create({
data: {
email,
name,
position: count + 1,
referralCode: nanoid(8), // 8-char unique code: "aB3xK9mZ"
referredById,
},
});
// Bump referrer's position if applicable:
if (referredById) {
await promoteReferrer(referredById);
}
// Send welcome email with referral link:
await sendWelcomeEmail({
email,
name,
position: entry.position,
referralCode: entry.referralCode,
referralLink: `${process.env.NEXT_PUBLIC_APP_URL}?ref=${entry.referralCode}`,
});
return Response.json({
success: true,
position: entry.position,
referralCode: entry.referralCode,
});
}
// Promote referrer when they get a new referral:
async function promoteReferrer(referrerId: string) {
const referrer = await db.waitlistEntry.findUnique({
where: { id: referrerId },
include: { _count: { select: { referrals: true } } },
});
if (!referrer) return;
const referralCount = referrer._count.referrals + 1;
// Move up 5 positions per referral:
const positionsToGain = 5;
const newPosition = Math.max(1, referrer.position - positionsToGain);
// Update referrer:
await db.waitlistEntry.update({
where: { id: referrerId },
data: {
position: newPosition,
referralCount,
},
});
}
Email Templates
// lib/email/waitlist.tsx — using React Email:
import { Html, Head, Body, Container, Text, Link, Button } from '@react-email/components';
interface WelcomeEmailProps {
name?: string;
position: number;
referralLink: string;
referralCode: string;
}
export function WelcomeEmail({ name, position, referralLink, referralCode }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f9fafb' }}>
<Container style={{ maxWidth: '600px', margin: '40px auto', padding: '24px', backgroundColor: '#fff' }}>
<Text style={{ fontSize: '24px', fontWeight: 'bold' }}>
You're on the list! 🎉
</Text>
<Text>
{name ? `Hey ${name}!` : 'Hey!'} You're <strong>#{position}</strong> on the waitlist.
</Text>
<Text>
Move up faster by sharing your referral link. Each friend who signs up moves you up 5 spots.
</Text>
<Button
href={referralLink}
style={{
backgroundColor: '#000',
color: '#fff',
padding: '12px 24px',
borderRadius: '8px',
display: 'inline-block',
}}
>
Share Your Link
</Button>
<Text style={{ color: '#6b7280', fontSize: '14px' }}>
Your referral code: <code>{referralCode}</code>
</Text>
<Text style={{ color: '#6b7280', fontSize: '14px' }}>
{referralLink}
</Text>
</Container>
</Body>
</Html>
);
}
// Send via Resend:
// lib/email/index.ts
import { Resend } from 'resend';
import { WelcomeEmail } from './waitlist';
import { render } from '@react-email/render';
const resend = new Resend(process.env.RESEND_API_KEY!);
export async function sendWelcomeEmail({
email, name, position, referralCode, referralLink,
}: {
email: string;
name?: string;
position: number;
referralCode: string;
referralLink: string;
}) {
await resend.emails.send({
from: 'Your App <hello@yourdomain.com>',
to: email,
subject: `You're #${position} on the waitlist`,
html: render(<WelcomeEmail name={name} position={position} referralCode={referralCode} referralLink={referralLink} />),
});
}
The Waitlist Page Component
// app/waitlist/page.tsx — landing page form:
'use client';
import { useState } from 'react';
export default function WaitlistPage() {
const [email, setEmail] = useState('');
const [name, setName] = useState('');
const [result, setResult] = useState<{
position?: number;
referralCode?: string;
error?: string;
} | null>(null);
const [loading, setLoading] = useState(false);
// Check for referral code in URL:
const ref = typeof window !== 'undefined'
? new URLSearchParams(window.location.search).get('ref')
: null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const res = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, name, referralCode: ref }),
});
const data = await res.json();
setResult(data);
setLoading(false);
};
if (result?.position) {
const referralLink = `${window.location.origin}?ref=${result.referralCode}`;
return (
<div className="text-center p-8">
<h2 className="text-2xl font-bold">You're #{result.position}! 🎉</h2>
<p className="mt-2 text-gray-600">Share to move up the list:</p>
<div className="mt-4 flex gap-2">
<input
value={referralLink}
readOnly
className="flex-1 rounded border p-2 text-sm"
/>
<button
onClick={() => navigator.clipboard.writeText(referralLink)}
className="rounded bg-black text-white px-4 py-2"
>
Copy
</button>
</div>
<p className="mt-4 text-sm text-gray-500">
Each friend who signs up moves you up 5 spots.
</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-8 space-y-4">
<h1 className="text-3xl font-bold">Join the waitlist</h1>
<p className="text-gray-600">Be first when we launch. Share to move up the queue.</p>
{ref && (
<p className="text-sm text-green-600">✓ Referred by a friend — you'll both benefit!</p>
)}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name (optional)"
className="w-full rounded border p-3"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
className="w-full rounded border p-3"
/>
<button
type="submit"
disabled={loading}
className="w-full rounded bg-black text-white p-3 font-semibold"
>
{loading ? 'Joining...' : 'Join the waitlist →'}
</button>
</form>
);
}
Option 3: ReferralHero (Managed Service)
Time: ~1 hour Cost: $79/month (Pro) Best for: when you want advanced analytics, A/B testing, and fraud detection
ReferralHero handles the referral mechanics and you embed a widget:
<!-- Embed on landing page: -->
<script src="https://app.referralhero.com/sdk.js" data-id="your-campaign-id"></script>
<div id="rhwidget-ot" class="ReferralHero"></div>
// Webhook when user completes a referral action:
// app/api/webhooks/referralhero/route.ts
export async function POST(req: Request) {
const { event, referrer_email, new_user_email } = await req.json();
if (event === 'referral_converted') {
// Grant reward to referrer:
await grantReferralReward(referrer_email);
}
return new Response('OK');
}
ReferralHero vs custom:
- Custom: free, full control, your data, 2-4 hours to build
- ReferralHero: $79/month, fraud detection, A/B test rewards, analytics dashboard
For early-stage (<1,000 signups), custom is fine. For a serious launch or marketing campaign, ReferralHero's fraud detection is worth paying for — fake accounts inflating referral counts is a real problem.
Converting Waitlist → Users
When you're ready to open access:
// Admin endpoint — grant access in waves:
// app/api/admin/waitlist/release/route.ts
export async function POST(req: Request) {
const { count, prioritizeReferrers } = await req.json();
const entries = await db.waitlistEntry.findMany({
where: { earlyAccess: false },
orderBy: prioritizeReferrers
? [{ referralCount: 'desc' }, { position: 'asc' }] // Referrers first
: { position: 'asc' }, // Pure queue order
take: count,
});
// Grant access:
await db.waitlistEntry.updateMany({
where: { id: { in: entries.map((e) => e.id) } },
data: { earlyAccess: true, accessGrantedAt: new Date() },
});
// Send access emails:
for (const entry of entries) {
await sendEarlyAccessEmail(entry.email, entry.name);
}
return Response.json({ granted: entries.length });
}
Wave strategy:
- Wave 1 (100 users): top referrers — most engaged, best word-of-mouth
- Wave 2 (500 users): next in queue — keep momentum
- Wave 3 (full open): announce publicly with social proof from waves 1-2
Quick Add: Boilerplate-Specific Integration
ShipFast:
- Add WaitlistEntry model to schema (MongoDB or Prisma)
- Create /api/waitlist route
- Replace ShipFast landing page with waitlist form
- Use Resend (pre-configured in ShipFast)
Makerkit:
- Add WaitlistEntry model to Prisma schema
- Create Supabase-auth-free endpoint (public, no session required)
- Add /waitlist page outside the auth middleware
- Makerkit uses Resend — wire it up to sendWelcomeEmail
Supastarter:
- Add WaitlistEntry to Supabase schema (raw SQL migration)
- Create public API route (outside Supabase auth check)
- Use Resend (pre-configured)
T3 Stack:
- Add Prisma schema additions above
- Create /api/waitlist (no auth requirement on this route)
- Use Resend or any email package
Common Mistakes That Kill Waitlist Conversions
A waitlist is only valuable if the people who sign up actually convert to paying users. Most waitlists fail not because of the signup form, but because of what happens after signup and how the access rollout is managed.
The most common mistake: letting the waitlist go cold. Signups happen during a launch push — a Product Hunt listing, a Twitter thread, a newsletter feature. Then the founder goes heads-down building for two months with no communication to the waitlist. When access finally opens, 60% of the emails bounce or go unanswered because the subscribers forgot who you are and why they signed up. The fix is simple: send one email per week during the building phase. Brief product updates — "here's what we built this week" — keep your list warm and give subscribers a reason to share the referral link again.
The second common mistake: not segmenting by referral activity during early access rollout. Your power users are the top referrers — the people who genuinely believe in your product enough to tell their network. Giving early access in pure queue order means your first users are the earliest random signups, not your advocates. Prioritize top referrers in Wave 1; they're the users most likely to give you detailed feedback and become case studies.
The third mistake: no referral reward beyond "move up the queue." Moving up the queue is a weak incentive for people who don't mind waiting. Add a tangible reward for the top referrers: a free first month, lifetime discount, premium tier for X months, or private beta access with direct founder communication. The reward doesn't need to be expensive — people respond to exclusivity and recognition more than monetary value.
Integrating Waitlist with Your Analytics
A waitlist without conversion tracking is a vanity metric. The data you need: where did signups come from (Twitter, Product Hunt, newsletter, direct referral), what's the referral multiplier (average referrals per signup), and what's the waitlist-to-activation conversion rate (signups who eventually become active users).
For attribution, capture the UTM source when someone visits the waitlist page and store it with the WaitlistEntry record. This lets you see which marketing channels produced the most signups and — more importantly — which channels produced the most referral-active signups. A channel that drives high-quantity but low-engagement signups is less valuable than a smaller channel whose signups actively share.
PostHog or Plausible can track funnel analytics from waitlist page visit to signup to referral share to early access click. Instrumenting this funnel identifies where you're losing potential signups — if 80% of visitors start the form but only 40% submit, the form itself is the problem. If 100% submit but only 5% share the referral link, the referral incentive isn't compelling enough.
Fraud Prevention in Referral Programs
Referral programs are systematically gamed. Understanding the attack vectors before launch lets you add friction in the right places without degrading the experience for legitimate users.
The most common attack is fake email addresses. Someone creates 20 throwaway email accounts to inflate their referral count and jump to the front of the queue. Detection: look for referral clusters from the same IP address, the same email domain pattern (user1@, user2@, user3@ at the same domain), or email addresses created within minutes of each other. A simple check: compare the email's domain against a list of known disposable email providers (mailinator.com, guerrillamail.com, temp-mail.org) and flag or reject those.
The second attack is the same person using multiple devices to sign up from different browsers. Browser fingerprinting (via a service like FingerprintJS) or IP-based rate limiting reduces this without requiring a login. Store the IP address with each WaitlistEntry and flag entries that share an IP address with more than three others.
For high-value waitlists (products with real demand and meaningful rewards for top referrers), add email verification before counting a referral. The flow: signup → send verification email → click link → verified status → referral is counted. Unverified signups add to the raw count but don't generate position boosts. This eliminates most fake-account attacks because verifying 20 throwaway email accounts requires real effort.
Email verification also improves your list quality: people who click a verification link are genuinely interested, not just passively curious. Your conversion rate from verified waitlist to paying user is significantly higher than from unverified signups. The friction cost is real — verification reduces total signup volume by 20-40% — but the remaining signups are higher-intent.
For most early-stage waitlists, light fraud detection (disposable email blocking, IP rate limiting) is sufficient. The sophisticated fraud prevention is only worth implementing when the referral rewards are valuable enough to attract organized gaming — lifetime discounts, significant cash rewards, or equity. A reasonable heuristic: if the top referral reward is worth more than $50, add email verification. If it's worth more than $200, add IP analysis. If you're offering lifetime deals or significant equity through referral programs, treat fraud prevention as a first-class requirement and consider ReferralHero's built-in fraud detection rather than building custom detection logic.
A waitlist with viral mechanics is one of the highest-leverage pre-launch investments a SaaS founder can make. The marginal cost of adding referral tracking to an existing waitlist form is a few hours of development; the upside — a 2-3x larger launch list from network effects — can mean the difference between a launch that gets noticed and one that doesn't. Every major SaaS boilerplate supports the pattern described here with minimal customization.
Find SaaS boilerplates with launch tools built in at StarterPick.
See how email tools integrate with waitlist flows: React Email vs Resend vs Loops for SaaS email 2026.
Learn how to launch your SaaS with zero cost: Production SaaS with free tools 2026.
Find the best boilerplates to build your SaaS on: Best SaaS boilerplates 2026.