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

  1. Create a project at supabase.com.
  2. In the SQL Editor, run the migrations in order — they are not idempotent, so run each exactly once:
MigrationCreates
001_initial.sqlAll tables, roles, row-level security, the attachments bucket
002_bootstrap_first_admin.sqlPromotes the first account to admin
003_widget_keys.sqlWidget keys on apps — required for the embeddable widget
004_logos_and_media.sqlLogo bucket, plus video support and the 50 MB attachment limit
  1. Under Auth → Providers, enable Email for magic links. Google and GitHub are optional.
  2. Under Auth → URL Configuration, add http://localhost:3000/auth/callback to the redirect URLs — and your production callback later.
  3. Optionally run supabase/seed.sql for demo apps.
Skipping 003 or 004 breaks features silently
Without 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

VariableRequiredWhat it does
NEXT_PUBLIC_SUPABASE_URLYesYour Supabase project URL. Also the base for public logo URLs.
NEXT_PUBLIC_SUPABASE_ANON_KEYYesPublic key for browser and server requests, scoped by row-level security.
SUPABASE_SERVICE_ROLE_KEYYesServer-only. Powers the widget, API keys, and logo uploads. Never expose it.
NEXT_PUBLIC_APP_URLIn productionYour public origin. Used in embed snippets, email links, and API responses.
RESEND_API_KEYNoEnables email. Without it, emails are skipped with a console warning.
RESEND_FROM_EMAILNoSender address. Defaults to a Resend test sender.
The service role key bypasses all database security
It must never appear in client code or in any variable prefixed NEXT_PUBLIC_. Anyone holding it has unrestricted read and write access to every table.
Set NEXT_PUBLIC_APP_URL before sharing anything
It falls back to 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

  1. Run npm run dev and open http://localhost:3000.
  2. Sign in with your email — this creates your profile as a customer.
  3. 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

  1. Go to Admin → Apps and create one per product you support.
  2. The slugbecomes the public board URL, so pick it carefully — it can't be changed from the UI afterwards.
  3. Optionally upload a logo (2 MB max) — it appears on the board and in the widget.
  4. 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:

  1. Open a ticket at /my/tickets/new, with a screenshot attached.
  2. Confirm it appears in /inbox, then assign it to yourself and reply.
  3. Confirm the status flipped to Pending, and that you received the reply email.
  4. Post an idea on /b/{slug}, vote on it, and comment.
  5. Set it to Planned from /feedback, then check it appears on the roadmap. Set it to Done and check the changelog.
  6. Open /widget-demo and submit a ticket through the widget.

8. Deploy

  1. Push to GitHub and import the repo on Vercel.
  2. Set all environment variables, with NEXT_PUBLIC_APP_URL as your real domain.
  3. Add https://your-domain/auth/callback to the Supabase redirect URLs.
  4. Point Resend at a verified domain.
  5. 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.