Minimal SaaS for small trucking and logistics companies: track fleet compliance documents (insurance, inspections, driver licenses, permits) and get email reminders 30/15/7/1 days before anything expires.
- Next.js 15 (App Router, server actions) + TypeScript + Tailwind CSS
- Prisma ORM — SQLite for local dev, switch to Postgres for production
- Auth: email + password with signed JWT session cookie (no third-party service)
- Payments: Lemon Squeezy subscription ($29/month, 14-day free trial without card)
- Emails: Resend (falls back to console logging when not configured)
- Reminders: internal daily cron (node-cron) or external cron hitting an endpoint
npm install
cp .env.example .env # then edit values (AUTH_SECRET at minimum)
npm run db:push # creates dev.db (SQLite)
npm run devOpen http://localhost:3000, create an account, and you get a 14-day trial with full access. Without RESEND_API_KEY, reminder emails are printed to the console.
See .env.example. Minimum for production:
| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
AUTH_SECRET |
Long random string for session cookies |
APP_URL |
Public URL, e.g. https://fleetguard.app |
LEMONSQUEEZY_API_KEY / LEMONSQUEEZY_STORE_ID / LEMONSQUEEZY_VARIANT_ID / LEMONSQUEEZY_WEBHOOK_SECRET |
Payments |
RESEND_API_KEY / EMAIL_FROM |
Reminder emails |
CRON_SECRET |
Protects /api/cron/reminders |
ENABLE_INTERNAL_CRON |
1 to run the daily 08:00 sweep in-process (VPS/Railway/Render) |
- Create a store, then a Product "Fleet plan" with a $29/month subscription
variant. Copy the store ID into
LEMONSQUEEZY_STORE_IDand the variant ID intoLEMONSQUEEZY_VARIANT_ID. - Create an API key (Settings → API) →
LEMONSQUEEZY_API_KEY. - Add a webhook (Settings → Webhooks) pointing to
<APP_URL>/api/lemonsqueezy/webhookwith eventssubscription_created,subscription_updated,subscription_expired. The signing secret you choose goes intoLEMONSQUEEZY_WEBHOOK_SECRET. - For local testing enable "Test mode" in Lemon Squeezy and use a tunnel
(e.g.
ngrok http 3000) so webhooks can reach your machine.
- VPS / Railway / Render: set
ENABLE_INTERNAL_CRON=1— a daily sweep runs at 08:00 server time inside the app. - Vercel / serverless: leave it unset and schedule a daily request to
GET /api/cron/reminderswith headerAuthorization: Bearer <CRON_SECRET>(Vercel Cron or cron-job.org).
- Switch
prisma/schema.prismadatasource provider topostgresqland pointDATABASE_URLat a managed Postgres (Neon and Supabase have free tiers), then runnpm run db:push. - File uploads: on Vercel, create a Blob store (Storage tab) and the
BLOB_READ_WRITE_TOKENenv var it provides makes uploads go to Vercel Blob automatically. Locally (or on a VPS) files land inuploads/on disk. - Reminders on Vercel:
vercel.jsonschedules a daily cron that calls/api/cron/reminders. Set theCRON_SECRETenv var in Vercel — its cron sends it automatically as the Authorization bearer token. - Other deploy targets that work out of the box: Railway, Render, Fly.io, any
VPS (
npm run build && npm start) — setENABLE_INTERNAL_CRON=1there.
prisma/schema.prisma Data model (Company, Vehicle, Driver, Document)
src/lib/ db, auth, lemonsqueezy, email, storage, expiry, reminders
src/app/page.tsx Landing page with pricing
src/app/(auth)/ Signup / login
src/app/app/ Authenticated app: dashboard, documents, vehicles,
drivers, billing
src/app/api/lemonsqueezy/webhook Lemon Squeezy webhook
src/app/api/cron/reminders External cron endpoint
src/app/api/files/[name] Authenticated file downloads
src/instrumentation.ts Internal daily cron