Auto-text customers when you miss their call. Stop losing $500+ leads to voicemail.
The Problem: Small businesses miss 62% of incoming calls. Each missed call is a potential $500+ customer walking away to a competitor who answered.
The Solution: CallCatcher gives you a dedicated business phone number. When you miss a call, we instantly text the customer: "Sorry we missed you! How can we help?" — keeping the lead warm until you can call back.
| Feature | What It Does |
|---|---|
| Instant Auto-Text | Texts customer within seconds of missed call |
| Smart Templates | Personalized messages with your business name & hours |
| Conversation Inbox | Continue the conversation via text from your dashboard |
| Revenue Tracking | See how much money you're saving (leads × $500) |
| 14-Day Free Trial | Test with real customers, no credit card required |
1. Customer calls your CallCatcher number
2. Call forwards to your personal phone
3. If you miss it → Customer gets instant text
4. Customer replies → You see it in your dashboard
5. You call back → Lead saved!
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Customer Call │────▶│ CallCatcher │────▶│ Your Phone │
└─────────────────┘ │ (Twilio) │ └─────────────────┘
└────────┬────────┘ │
│ │
Missed? Answered?
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Auto-Text Sent │ │ Normal Call │
│ "Sorry we..." │ │ (No action) │
└─────────────────┘ └─────────────────┘
- Frontend: Next.js 14 (App Router), React 18, Tailwind CSS
- Database: Supabase (PostgreSQL + Auth + Row Level Security)
- Phone/SMS: Twilio (Phone number provisioning, Voice, SMS)
- Payments: Stripe (Subscriptions, Billing Portal)
- Deployment: Vercel (recommended)
- Node.js 18+
- npm or yarn
- Supabase account (free tier works)
- Twilio account (trial works for testing)
- Stripe account (test mode)
- ngrok (for Twilio webhooks in development)
git clone https://github.com/yourusername/callcatcher.git
cd callcatcher
npm installCreate .env.local:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Twilio
TWILIO_ACCOUNT_SID=ACxxxxxxxxxx
TWILIO_AUTH_TOKEN=your-auth-token
# Stripe
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxx
STRIPE_PRICE_10_CREDITS=price_xxxxxxxxxx
# Base URL (use ngrok for local dev)
NEXT_PUBLIC_BASE_URL=https://your-ngrok-url.ngrok-free.dev
# Email (optional)
RESEND_API_KEY=re_xxxxxxxxxx
EMAIL_FROM=noreply@yourdomain.comRun migrations in your Supabase SQL Editor:
-- See supabase/migrations/ for full schema
-- Key tables: users, profiles, messages, calls, opt_outsOr use the Supabase MCP if you have it configured:
# Migrations are in supabase/migrations/
# Run them in order: initial_schema, add_stripe_customer_id, sync_auth_usersngrok http 3000Update NEXT_PUBLIC_BASE_URL in .env.local with your ngrok URL.
npm run devOpen http://localhost:3000.
src/
├── app/
│ ├── page.tsx # Landing page
│ ├── onboarding/ # Business profile setup
│ │ ├── page.tsx # Step 1: Business info
│ │ └── phone/page.tsx # Step 2: Phone provisioning
│ ├── dashboard/
│ │ ├── page.tsx # Main dashboard
│ │ ├── settings/ # User settings
│ │ └── conversations/[phone]/ # Message thread view
│ ├── api/
│ │ ├── twilio/ # Twilio webhooks
│ │ │ ├── voice/route.ts # Incoming call handler
│ │ │ ├── sms/route.ts # Incoming SMS handler
│ │ │ └── status/route.ts # Call status callback
│ │ └── stripe/webhook/ # Stripe subscription webhooks
│ └── auth/callback/ # Supabase OAuth callback
├── components/
│ ├── DashboardContent.tsx # Dashboard UI
│ ├── ConversationsList.tsx # Message inbox
│ ├── TrialBanner.tsx # Subscription status
│ └── ...
├── lib/
│ ├── supabase/ # Supabase client setup
│ ├── twilio.ts # Twilio client
│ ├── twilio-actions.ts # Phone provisioning
│ ├── stripe.ts # Stripe client
│ └── stripe-actions.ts # Checkout/portal sessions
└── types/
└── database.ts # TypeScript types for DB
Configure these in Twilio Console or they're auto-set when provisioning:
| Webhook | URL | Purpose |
|---|---|---|
| Voice | {BASE_URL}/api/twilio/voice |
Handle incoming calls |
| SMS | {BASE_URL}/api/twilio/sms |
Handle incoming texts |
| Status Callback | {BASE_URL}/api/twilio/status |
Track call status (missed/answered) |
Create these in Stripe Dashboard:
- Monthly Subscription - $29/month
- Set
STRIPE_PRICE_10_CREDITSto the price ID
The webhook handles:
checkout.session.completed- Activate subscriptioncustomer.subscription.updated- Status changescustomer.subscription.deleted- Cancellation
- Push to GitHub
- Import in Vercel
- Add environment variables
- Deploy
- Update
NEXT_PUBLIC_BASE_URLto your production domain - Use production Stripe keys
- Ensure Twilio webhooks point to production URLs
"VoiceUrl is not valid: http://localhost..."
Twilio can't reach localhost. You need ngrok:
ngrok http 3000
# Update NEXT_PUBLIC_BASE_URL with the ngrok URLThe auth user sync trigger might be missing. Run:
-- In Supabase SQL Editor
INSERT INTO public.users (id, email)
SELECT id, email FROM auth.users
ON CONFLICT (id) DO NOTHING;Clear the Next.js cache:
rm -rf .next
npm run dev- US phone numbers only - Twilio restriction for SMS
- Single phone forwarding - Forwards to one personal number
- No MMS support - Text only, no picture messages
- 14-day trial - Requires subscription after trial
$29/month after 14-day free trial. Includes unlimited missed call texts.
No. You get a new CallCatcher number. Update your Google Business listing with the new number.
Your CallCatcher number is released. Customers calling it will get a disconnected message.
Yes. All data is stored in Supabase with Row Level Security. Each user can only access their own data.
MIT
Built with Next.js, Supabase, Twilio, and Stripe
Stop losing leads. Start catching calls.
