Monitor Reddit and Hacker News for high-intent conversations where people are asking about problems your product solves. LaunchRadar scores each match with AI, surfaces the best opportunities in a dashboard, and emails you a daily digest with ready-to-use reply suggestions.
Live demo: https://launchradar-five.vercel.app
- Onboarding — describe your product in one sentence; OpenAI infers your target customer and generates keywords and subreddits automatically
- Instant first scan — as soon as onboarding completes, a scan runs immediately and results appear within 30–60 seconds
- Daily fetch — a cron job scrapes Reddit and Hacker News for new posts, scores each one 0–100 for relevance and tags intent (high / medium / low)
- Dashboard — browse and filter opportunities; each card shows the post title (clickable link), a body preview, AI reasoning, and an inline suggested reply with a one-click copy button
- Digest email — receive a daily summary of the top opportunities with AI-generated reply suggestions
- Manual scan — trigger an on-demand scan any time from the Settings page; configure how many HN posts to fetch per scan (default 50)
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) + TypeScript |
| Database | PostgreSQL via Supabase |
| ORM | Prisma v7 with @prisma/adapter-pg |
| Auth | Supabase SSR (@supabase/ssr) |
| AI | OpenAI (GPT-4o-mini for scoring, GPT-4o for keywords + reply generation) |
| Payments | Stripe (hosted Checkout, $19/month) |
| Resend + React Email | |
| Caching | Upstash Redis (deduplication) |
| UI | Shadcn/ui + Tailwind CSS v4 |
| Hosting | Vercel (serverless + cron jobs) |
- Node.js 20+
- A Supabase project (free tier works)
- An OpenAI API key
- A Stripe account (test mode is fine)
- A Resend account (free tier works)
- An Upstash Redis database (free tier works)
git clone https://github.com/your-username/launchradar.git
cd launchradar
npm install- Create a new project at supabase.com
- Go to Project Settings → Database → Connection string
- Copy the Transaction Pooler URL (port
6543) — this is yourDATABASE_URL - Copy the Direct connection URL (port
5432) — this is yourDIRECT_URL(used only for migrations) - From Project Settings → API, copy your
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY, andSUPABASE_SERVICE_ROLE_KEY
Why Transaction Pooler? Serverless functions can't hold persistent database connections. The Transaction Pooler (port 6543) handles connection management; the direct URL (port 5432) would cause
P1001errors in production.
- Create a product and price in the Stripe Dashboard (or use test mode)
- Set the price to $19/month recurring — copy the
price_...ID asSTRIPE_PRICE_ID - Copy your Secret key (
sk_test_...) asSTRIPE_SECRET_KEY - You'll add
STRIPE_WEBHOOK_SECRETafter setting up the webhook (step 7)
- Create an account at resend.com
- Copy your API key as
RESEND_API_KEY - For
RESEND_FROM_EMAIL, useonboarding@resend.devuntil you verify a custom domain
- Create a Redis database at upstash.com
- Copy the REST URL and REST Token as
UPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN
Copy the example file and fill in all values:
cp .env.example .env.local# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# Database — MUST be Transaction Pooler URL (port 6543)
DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres?pgbouncer=true
# Direct URL (port 5432) — used only by `prisma migrate`
DIRECT_URL=postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
# OpenAI
OPENAI_API_KEY=sk-proj-...
# Upstash Redis
UPSTASH_REDIS_REST_URL=https://...
UPSTASH_REDIS_REST_TOKEN=...
# Resend
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=onboarding@resend.dev
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PRICE_ID=price_...
STRIPE_WEBHOOK_SECRET=whsec_... # leave blank until step 7
# Cron authentication (any random secret)
CRON_SECRET=your-random-secret # generate with: openssl rand -base64 32
# Registration gate — set to 'true' to allow new sign-ups, 'false' to lock registration
NEXT_PUBLIC_REGISTRATION_OPEN=truenpx prisma migrate devnpm run devOpen http://localhost:3000.
In a separate terminal:
stripe listen --forward-to localhost:3000/api/stripe/webhookCopy the whsec_... secret that appears and set it as STRIPE_WEBHOOK_SECRET in .env.local.
# Fetch Reddit + HN posts, score them, store opportunities (all users)
curl -X POST http://localhost:3000/api/cron/fetch-posts \
-H "Authorization: Bearer $CRON_SECRET"
# Send digest emails to all users with email enabled
curl -X POST http://localhost:3000/api/cron/send-digests \
-H "Authorization: Bearer $CRON_SECRET"
# On-demand scan for the logged-in user (session auth — run from browser or with session cookie)
curl -X POST http://localhost:3000/api/opportunities/refresh \
-H "Cookie: <paste session cookie from browser>"- Push to GitHub and import the repo in Vercel
- Set all environment variables in Project Settings → Environment Variables (use production Stripe keys and your live domain for
NEXT_PUBLIC_APP_URL) - Add a Stripe webhook endpoint in the Stripe Dashboard pointing to
https://your-domain.com/api/stripe/webhookwith thecheckout.session.completedandcustomer.subscription.deletedevents. Copy the signing secret toSTRIPE_WEBHOOK_SECRET - Cron jobs are pre-configured in
vercel.jsonto run daily at 08:00 UTC
├── app/
│ ├── page.tsx Landing page
│ ├── dashboard/page.tsx Dashboard (server component)
│ ├── onboarding/page.tsx Single-input onboarding form
│ ├── settings/page.tsx Settings page
│ ├── auth/
│ │ ├── login/ Supabase email/password auth
│ │ └── register/ Supabase email/password auth
│ └── api/
│ ├── cron/
│ │ ├── fetch-posts/ Scrapes Reddit + HN, scores with OpenAI, stores opportunities
│ │ └── send-digests/ Sends daily digest emails via Resend
│ ├── stripe/
│ │ ├── checkout/ Creates Stripe Checkout Session
│ │ └── webhook/ Handles Stripe events (raw body — do not change to .json())
│ ├── opportunities/
│ │ ├── [id]/reply/ Marks opportunity as replied
│ │ ├── refresh/ On-demand per-user fetch + score
│ │ └── count/ Returns opportunity count for current user
│ ├── onboarding/ Generates keywords + marks onboarding complete
│ ├── settings/ PATCH profile settings
│ └── feedback/ Records opportunity relevance feedback
│
├── components/
│ ├── DashboardClient.tsx Filter tabs + opportunity feed
│ ├── OpportunityCard.tsx Individual opportunity with action buttons
│ ├── ReplyModal.tsx AI-suggested reply viewer
│ ├── BuyModal.tsx Stripe subscription checkout modal
│ ├── SettingsClient.tsx Settings forms
│ ├── StatsBar.tsx Stats counters (found / replied / skipped)
│ └── Header.tsx Top nav
│
├── lib/
│ ├── db/client.ts Prisma singleton — always import from here
│ ├── supabase/
│ │ ├── server.ts Server-side Supabase client
│ │ └── client.ts Browser Supabase client
│ ├── scorer.ts OpenAI relevance scoring (0–100 + intent + reply suggestions)
│ ├── keyword-generator.ts OpenAI keyword + subreddit generation
│ ├── refresh-opportunities.ts Shared fetch/score/save logic for a single user
│ ├── reddit.ts Reddit public .json API fetcher (no OAuth needed)
│ ├── hn.ts Hacker News Algolia API fetcher
│ ├── digest.ts Selects top opportunities for digest email
│ └── email-templates/digest.tsx React Email digest template
│
└── prisma/
└── schema.prisma Database schema (Profile, Opportunity, Feedback)
Profile — one per user; id matches the Supabase auth UID
Opportunity — one per matched post; userId → Profile; stores relevance score (0–100), intent level, AI reasoning, and suggested replies
Feedback — records thumbs-up/down on each opportunity for future model improvements
See prisma/schema.prisma for the full schema.
- No Reddit OAuth — all Reddit fetching uses the public
.jsonAPI (reddit.com/r/{sub}/new.json). No credentials required. - Prisma v7 requires an adapter — always use
lib/db/client.ts; never callnew PrismaClient()without the@prisma/adapter-pgadapter. - Stripe webhook uses
request.text()— the raw body is required for signature verification;request.json()breaks this. - Auth model is
Profile— there is noUsermodel in Prisma; the Supabase auth UID is used as theProfile.id.
Pull requests are welcome. For significant changes, open an issue first to discuss what you'd like to change.
- Fork the repo and create a branch:
git checkout -b my-feature - Make your changes and test locally
- Open a pull request with a clear description of what changed and why
MIT