Automatic review requests for sole traders and local trades.
Send SMS and email review invitations after every job — without remembering to follow up. Built for plumbers, electricians, cleaners, roofers, and other small service businesses.
Live: reviewping.co.uk
- What is ReviewPing?
- Features
- Technical highlights
- Architecture
- Tech stack
- Prerequisites
- Local setup
- Scripts
- Cron jobs
- Project structure
- Documentation
- Deployment
- Contributing
- License
ReviewPing is a single-purpose SaaS for getting more online reviews. It is not a CRM — every feature exists to support one outcome:
Help sole traders automatically get more Google (and other platform) reviews after each job.
- Add a customer (name + phone or email)
- Log a job and mark it complete (or send manually)
- System sends an SMS or email with a tracked review link
- Customer clicks through and leaves a review
- You see sent / clicked status in the dashboard
- 14-day free trial with 40 credits
- Credit costs: SMS = 4 credits, email = 1 credit
- Plans: Starter £9/mo · Growth £19/mo · Pro £39/mo (see
lib/billing/config.ts)
- Email + password signup with email verification
- OTP magic-link sign-in (8-digit code)
- Password reset flow
- Session middleware on all routes
See docs/SUPABASE_AUTH.md for Supabase dashboard configuration.
- Create, edit, delete, search, and filter customers
- CSV import and mobile contacts import
- Quick-paste customer add (settings-gated)
- Job logging with completion triggers for automation
- Manual send from customer or job dialogs
- Automated send on job completion (configurable delay and send window)
- Lifecycle:
pending→scheduled→sent/failed/cancelled - Cancel with lead-time rules, resend failed requests
- Filterable requests table
- Editable SMS and email message templates
- Custom template variables
- Default template selection per channel
- Pro plan: email builder
- Short tracked links at
/r/[code] - Click tracking and redirect to review site
- Optional testimonial collection mode
- Stripe subscriptions (monthly and yearly)
- Credit packs, rollover, low-credit warnings
- Plan-gated features (analytics on Growth+, team on Pro)
- Webhook-driven subscription sync
- Invite up to 5 team members
- Shared business context under owner profile
- Accept invites at
/invite/[token]
- Getting-started checklist (7 steps to first click)
- Analytics charts (Growth+ plan)
- Onboarding gates: profile, password, billing setup
- Vercel Cron jobs for background processing
- Webhooks: Stripe, Resend (Svix), Twilio
- Health check endpoint for uptime monitoring
- Rate limiting via Upstash Redis (in-memory fallback for local dev)
This section is for engineers and recruiters evaluating the codebase.
A single Next.js 16 App Router application — no separate backend service, no microservices. Mutations use Server Actions; reads use server components and cached queries.
- Supabase Auth for sign-in, sessions, and OTP (
lib/supabase/,middleware.ts) - Prisma ORM on Postgres for all application data (
prisma/schema.prisma) - Prisma is the schema source of truth; see docs/PRISMA.md
Atomic credit spend and refund logic in lib/billing/credits/spend.ts. Dispatch orchestration (billing check → template render → Resend/Twilio send) in lib/messaging/dispatch-review-request.ts.
No Redis job queue. Scheduled review processing uses Vercel Cron hitting bearer-authenticated API routes (lib/cron/handle-cron-request.ts):
- Promote pending requests (5-minute cancellation window)
- Generate automated requests from job rules
- Process due scheduled sends
| Service | Purpose |
|---|---|
| Stripe | Subscriptions, credit packs, webhooks |
| Resend | Transactional email (invites, review requests) |
| Twilio | SMS sending and delivery status callbacks |
| Cloudflare R2 | Logo and support attachment uploads |
| Upstash Redis | Distributed rate limiting |
| Google Web Risk | Review link safety checks |
- Google Web Risk API on review link changes (
lib/security/web-risk.ts) - Outbound URL validation before dispatch
- Cron secret with timing-safe comparison (
lib/cron/verify-cron-secret.ts) - Per-IP rate limits on cron and public redirect routes
- Webhook signature verification (Stripe, Svix/Resend, Twilio)
625 unit tests across 118 co-located Vitest test files under lib/**/*.test.ts, covering billing, requests, cron auth, webhooks, validation, and mappers. Run with npm test.
Sitemap, robots.txt, JSON-LD structured data, and machine-readable /llms.txt for crawlers.
flowchart TB
subgraph client [Client]
Browser[Browser]
Customer[Customer SMS/Email]
end
subgraph vercel [Next.js on Vercel]
App[App Router + Server Actions]
Cron[Cron API Routes]
Webhooks[Webhook Handlers]
PublicLink["/r/code redirect"]
end
subgraph data [Data and Auth]
SupabaseAuth[Supabase Auth]
Postgres[(Postgres via Prisma)]
end
subgraph external [External Services]
Stripe[Stripe]
Resend[Resend]
Twilio[Twilio]
R2[Cloudflare R2]
Upstash[Upstash Redis]
WebRisk[Google Web Risk]
end
Browser --> App
App --> SupabaseAuth
App --> Postgres
Cron --> Postgres
App --> Stripe
App --> Resend
App --> Twilio
App --> R2
App --> Upstash
App --> WebRisk
Webhooks --> Postgres
Customer --> PublicLink
PublicLink --> Postgres
sequenceDiagram
participant User as Tradesperson
participant App as ReviewPing
participant Cron as CronJobs
participant Channel as Resend_or_Twilio
participant Customer as Customer
User->>App: Add customer and job
User->>App: Mark job complete or manual send
App->>App: Create ReviewRequest pending or scheduled
Cron->>App: promote-pending-requests
Cron->>App: generate-automated-requests
Cron->>App: process-due-scheduled-requests
App->>Channel: Dispatch SMS or email with /r/code link
Channel->>Customer: Review invitation
Customer->>App: Click tracked link
App->>App: Record click redirect to review site
erDiagram
Profile ||--o{ Customer : owns
Profile ||--o{ Job : owns
Profile ||--o{ ReviewRequest : owns
Customer ||--o{ Job : has
Customer ||--o{ ReviewRequest : receives
Job ||--o{ ReviewRequest : triggers
Profile ||--o{ CreditBucket : has
CreditBucket ||--o{ CreditTransaction : logs
Profile ||--o{ TeamMember : manages
ReviewRequest ||--o| Testimonial : optional
| Layer | Technology | Key paths |
|---|---|---|
| Framework | Next.js 16 (App Router) | app/ |
| UI | React 19, Tailwind 4, shadcn/ui | components/ |
| API | Route handlers + Server Actions | app/api/, app/actions/ |
| Database | Prisma 6 on Postgres | prisma/, lib/db/ |
| Auth | Supabase (@supabase/ssr) |
lib/supabase/ |
| Resend | lib/email/ |
|
| SMS | Twilio | lib/sms/ |
| Payments | Stripe | lib/billing/ |
| Storage | Cloudflare R2 (S3-compatible) | lib/storage/ |
| Tests | Vitest | vitest.config.ts |
| Deploy | Vercel | — |
- Node.js 20+
- npm
- Supabase project (Auth + Postgres)
- Stripe account (test mode for local billing)
- Resend account (for email sending)
- Twilio account (optional — required for SMS)
- Cloudflare R2 bucket (optional — for business logo uploads)
git clone https://github.com/your-org/review-ping.git
cd review-ping
npm installCopy .env.example to .env.local:
cp .env.example .env.localFill in values from your service dashboards. Minimum tiers:
| Tier | Variables | What works |
|---|---|---|
| Boot + auth | DATABASE_URL, DIRECT_URL, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, NEXT_PUBLIC_SITE_URL |
Sign up, login, dashboard shell |
| Send messages | + RESEND_API_KEY, TWILIO_* |
Email and SMS review requests |
| Billing | + STRIPE_*, STRIPE_PRICE_* |
Subscriptions and credit packs |
| Cron (local) | + CRON_SECRET |
Background job endpoints |
| Prod hardening | + UPSTASH_REDIS_REST_* |
Distributed rate limits |
Generate a cron secret:
openssl rand -hex 32See .env.example for the full variable list with comments.
Get connection strings from Supabase: Project → Connect → ORMs → Prisma.
DATABASE_URL— pooled connection (port 6543,?pgbouncer=true)DIRECT_URL— direct connection (port 5432) for migrations only
Apply migrations:
npm run db:migrateAfter the first migrate, run supabase/sql/profiles_rls.sql in the Supabase SQL Editor (RLS policies — not managed by Prisma).
Configure the Supabase dashboard per docs/SUPABASE_AUTH.md:
- Set Site URL and Redirect URLs (
/auth/callback,/login/reset-password) - Enable Email provider with OTP (8-digit code)
- Turn Confirm email ON for signup flow
npm run devOpen http://localhost:3000.
| Command | Purpose |
|---|---|
npm run dev |
Start development server |
npm run build |
Prisma generate + production build |
npm run start |
Start production server |
npm test |
Run Vitest unit tests (625 tests, 118 files) |
npm run test:watch |
Vitest in watch mode |
npm run lint |
ESLint |
npm run db:migrate |
Unlock stale locks + apply pending migrations |
npm run db:migrate:deploy |
Production/CI migration apply |
npm run db:migrate:status |
Check migration state |
npm run db:generate |
Regenerate Prisma client |
npm run db:studio |
Open Prisma Studio |
Background processing runs via authenticated HTTP endpoints at /api/cron/*. In production, Vercel Cron triggers these on a schedule (see vercel.json_bk for reference — copy to vercel.json to enable).
All cron requests require:
Authorization: Bearer <CRON_SECRET>
Invalid or missing auth returns 401. Rate limiting (default 10 req/min per IP per route) returns 429.
| Route | Schedule | Purpose |
|---|---|---|
/api/cron/promote-pending-requests |
Every 5 min | Move manual pending requests to scheduled after ~5 min hold |
/api/cron/generate-automated-requests |
Hourly | Create scheduled requests from job automation rules |
/api/cron/process-due-scheduled-requests |
Every 5 min | Send due requests via Resend (email) or Twilio (SMS) |
/api/cron/health |
External monitor | Database liveness check |
Ensure the app is running and CRON_SECRET is set, then:
# Recommended order for a full cycle
curl -s -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/generate-automated-requests
curl -s -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/promote-pending-requests
curl -s -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/process-due-scheduled-requests
# Health check (safe to call anytime)
curl -s -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/health| Route | Provider | Purpose |
|---|---|---|
/api/webhooks/stripe |
Stripe | Subscription sync, invoice paid, credit pack grants |
/api/webhooks/resend |
Resend (Svix) | Email delivery events (bounce, complaint) |
/api/webhooks/twilio |
Twilio | SMS delivery status callbacks |
app/
(landing)/ # Marketing site (home, guides)
(auth)/ # Login, signup, password reset
(dashboard)/ # Authenticated app (customers, jobs, requests, etc.)
api/ # Cron routes, webhooks
actions/ # Server Actions (mutations)
r/[code]/ # Public tracked review link
invite/[token]/ # Team invite acceptance
components/ # UI (dashboard, landing, auth, billing, etc.)
lib/ # Business logic
billing/ # Stripe, credits, plan access
cron/ # Cron auth, rate limiting
email/ # Resend integration
sms/ # Twilio integration
messaging/ # Dispatch orchestration
requests/ # Review request lifecycle
jobs/ # Job automation
tracking/ # Click tracking, rate limits
supabase/ # Auth clients
security/ # URL validation, Web Risk
prisma/ # Schema and migrations
docs/ # Setup guides (Prisma, Auth, Profiles)
supabase/sql/ # RLS policies (manual post-migrate step)
| Guide | Contents |
|---|---|
| docs/PRISMA.md | Database setup, migrations, cron env vars, troubleshooting |
| docs/SUPABASE_AUTH.md | Auth dashboard config, OTP setup, flows |
| docs/PROFILES.md | Profile and business data model |
- Connect the repository to Vercel
- Set all environment variables from
.env.example - Run
npm run db:migrate:deployon deploy (or via a CI step) - Copy
vercel.json_bktovercel.jsonto enable cron schedules - Register webhook endpoints in each provider dashboard:
- Stripe →
https://yourdomain.com/api/webhooks/stripe - Resend →
https://yourdomain.com/api/webhooks/resend - Twilio → status callback set automatically at send time
- Stripe →
| Route | Cron expression |
|---|---|
promote-pending-requests |
*/5 * * * * |
generate-automated-requests |
0 * * * * |
process-due-scheduled-requests |
*/5 * * * * |
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-change) - Make your changes and add tests where appropriate
- Run
npm testandnpm run lint - Open a pull request with a clear description
MIT — Copyright (c) 2026 Jay
ReviewPing is not affiliated with or endorsed by Google, Stripe, Twilio, or any review platform.