Start here
Getting started
Stand up a Lunax workspace from nothing: database, environment, first admin, first app, and a working widget. Budget about twenty minutes.
1. Install
npm install cp .env.example .env.local
Lunax runs on Next.js with the App Router, React 19, Tailwind v4, and Supabase for both database and auth.
2. Set up Supabase
- Create a project at
supabase.com. - In the SQL Editor, run the migrations in order — they are not idempotent, so run each exactly once:
| Migration | Creates |
|---|---|
001_initial.sql | All tables, roles, row-level security, the attachments bucket |
002_bootstrap_first_admin.sql | Promotes the first account to admin |
003_widget_keys.sql | Widget keys on apps — required for the embeddable widget |
004_logos_and_media.sql | Logo bucket, plus video support and the 50 MB attachment limit |
- Under Auth → Providers, enable Email for magic links. Google and GitHub are optional.
- Under Auth → URL Configuration, add
http://localhost:3000/auth/callbackto the redirect URLs — and your production callback later. - Optionally run
supabase/seed.sqlfor demo apps.
003, the widget section of the keys page shows a warning instead of an embed snippet. Without 004, logo uploads fail and attachments are capped at the old 10 MB image-only limit.3. Environment variables
| Variable | Required | What it does |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Yes | Your Supabase project URL. Also the base for public logo URLs. |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Yes | Public key for browser and server requests, scoped by row-level security. |
SUPABASE_SERVICE_ROLE_KEY | Yes | Server-only. Powers the widget, API keys, and logo uploads. Never expose it. |
NEXT_PUBLIC_APP_URL | In production | Your public origin. Used in embed snippets, email links, and API responses. |
RESEND_API_KEY | No | Enables email. Without it, emails are skipped with a console warning. |
RESEND_FROM_EMAIL | No | Sender address. Defaults to a Resend test sender. |
NEXT_PUBLIC_. Anyone holding it has unrestricted read and write access to every table.http://localhost:3000. Leave it unset in production and your customers get emails with localhost links, and your embed snippets point at nothing.4. Email (optional locally)
Lunax sends transactional email through Resend. Without RESEND_API_KEY everything still works — emails are logged and skipped, which is usually what you want in development.
For production, verify a domain in Resend and set RESEND_FROM_EMAIL to an address on it. See Email notifications for exactly what gets sent.
5. Make yourself admin
- Run
npm run devand openhttp://localhost:3000. - Sign in with your email — this creates your profile as a customer.
- If you ran
002_bootstrap_first_admin.sqlafter signing in, you're already an admin. Otherwise run this once:
update public.profiles set role = 'admin' where email = 'you@example.com';
Refresh, and Admin appears in the header.
6. Create your first app
- Go to Admin → Apps and create one per product you support.
- The slugbecomes the public board URL, so pick it carefully — it can't be changed from the UI afterwards.
- Optionally upload a logo (2 MB max) — it appears on the board and in the widget.
- Open Keys & widget for the app to get the embed snippet and mint secret API keys.
7. Verify end to end
Walk one item through each subsystem before you invite anyone:
- Open a ticket at
/my/tickets/new, with a screenshot attached. - Confirm it appears in
/inbox, then assign it to yourself and reply. - Confirm the status flipped to Pending, and that you received the reply email.
- Post an idea on
/b/{slug}, vote on it, and comment. - Set it to Planned from
/feedback, then check it appears on the roadmap. Set it to Done and check the changelog. - Open
/widget-demoand submit a ticket through the widget.
8. Deploy
- Push to GitHub and import the repo on Vercel.
- Set all environment variables, with
NEXT_PUBLIC_APP_URLas your real domain. - Add
https://your-domain/auth/callbackto the Supabase redirect URLs. - Point Resend at a verified domain.
- Confirm at least two people hold the admin role.
Troubleshooting
Sign-in silently returns me to the login page
The callback URL isn't in the Supabase redirect list, or it doesn't match your origin exactly. The failure is silent by design, so check the Supabase auth logs.
Everything loads but nothing is protected
The Supabase environment variables are missing, so auth is skipped entirely. Check .env.local and restart the dev server.
The widget section shows a warning instead of a snippet
Migration 003_widget_keys.sqlhasn't run. Apply it, then reload the keys page.
Logo upload fails
Either 004_logos_and_media.sql hasn't run, or SUPABASE_SERVICE_ROLE_KEY is missing. The file must also be under 2 MB and a PNG, JPEG, WebP, GIF, or SVG.
Emails never arrive
Without RESEND_API_KEYthey're skipped — look for the console warning. With a key set, the sending domain must be verified in Resend.
Widget requests return 401
The app slug or widget key is wrong, or the app has the widget disabled. Copy a fresh snippet from the keys page.
- Widget returning 429? You've hit 8 tickets or 10 feedback posts per minute. Wait and retry.