HarmonyGift is a ritual gifting platform where creators generate personalized songs for loved ones (biographer → vibe selection → studio → public gift page).
This repo uses Next.js (App Router) + Tailwind + Supabase (Postgres) + Clerk + BullMQ worker.
Note: The current repo is on
next@16+react@19(route structure + behavior match the product spec).
- Node.js 20+ recommended
pnpminstalled
From the repo root:
pnpm installWorker dependencies:
pnpm -C worker installCreate a local env file:
cp .env.example .env.localFill at minimum:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYNEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEYCLERK_WEBHOOK_SECRETNEXT_PUBLIC_APP_URL(usehttp://localhost:3000for local)
Feature-specific (needed only when you test those features):
- Biographer + lyrics:
ANTHROPIC_API_KEY(optionalANTHROPIC_MODEL) - Queues/worker:
UPSTASH_REDIS_URL(Redis protocol URL for BullMQ) - Email delivery:
RESEND_API_KEY,RESEND_FROM_EMAIL - R2 uploads:
R2_*+R2_PUBLIC_URL - Audio synthesis:
ELEVENLABS_API_KEY, optionalELEVEN_MUSIC_ENABLED, and optionalELEVENLABS_VOICE_ID(fallback) - Payments:
STRIPE_*
- Create a Supabase project.
- Open SQL Editor in Supabase.
- Run these files in order:
supabase/migrations/0001_init.sqlsupabase/migrations/0002_rls.sql
More details: supabase/README.md
Dashboard uses Supabase Realtime. Ensure these tables are included in the supabase_realtime publication:
public.songspublic.activity
0002_rls.sql attempts to add them automatically, but check your Supabase dashboard if needed.
- Create a Clerk application.
- Copy keys into
.env.local:NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEY
Create a JWT template named supabase:
aud:authenticatedrole:authenticated- custom claim:
user_id = {{user.public_metadata.supabase_user_id}}
Recommended: configure the template to sign with HS256 using your Supabase project JWT secret (Supabase → Settings → API → JWT Secret).
Why: Supabase can then verify the Clerk token and expose claims via auth.jwt() for RLS policies.
This repo mirrors Clerk users into Supabase and writes the Supabase UUID into Clerk public metadata.
Webhook endpoint:
POST /api/webhooks/clerk
Webhook events:
user.createduser.updated
Local testing usually requires a tunnel (ex: ngrok):
ngrok http 3000Then set the webhook URL in Clerk to your ngrok URL + /api/webhooks/clerk.
Start Next.js:
pnpm devOpen:
http://localhost:3000→ redirects to sign-in- After sign-in:
/dashboard
If you see a warning about a missing Supabase session token, your Clerk JWT template or Supabase JWT verification is not configured correctly.
The worker is a separate, long-running process:
pnpm -C worker devUPSTASH_REDIS_URLmust be set
The worker automatically loads:
- repo root
.env/.env.local - and optionally
worker/.env/worker/.env.local(overrides)
Set:
ANTHROPIC_API_KEY- (optional)
ANTHROPIC_MODEL=claude-sonnet-4-6
Set:
UPSTASH_REDIS_URL(use Upstash Redis URL /rediss://..., not the REST URL)
Set:
RESEND_API_KEYRESEND_FROM_EMAIL
Set:
R2_ACCOUNT_IDR2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEYR2_BUCKET_NAMER2_PUBLIC_URL(public base URL for serving uploaded objects)
Set:
ELEVENLABS_API_KEY- (optional)
ELEVEN_MUSIC_ENABLED=falseto disable Eleven Music - (optional)
ELEVENLABS_VOICE_ID(used for beat+TTS fallback)
Notes:
- If
ELEVENLABS_API_KEYis set, the worker will prefer Eleven Music (paid feature) to generate a real music track. - If Eleven Music fails (or you disable it), the worker falls back to a beat+TTS pipeline, which requires
ELEVENLABS_VOICE_IDand beats in R2.
Upload beat mp3s to R2:
beats/afrobeat-001.mp3beats/rap-001.mp3beats/rnb-001.mp3beats/soul-001.mp3
Set:
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSTRIPE_PRICE_TOPUP_ID(orSTRIPE_PRICE_SUBSCRIPTION_ID)
Credits are granted from Stripe Price metadata:
- add
metadata.credits = <integer>on the Stripe Price
Webhook endpoint:
POST /api/webhooks/stripe
/create/biographer→ chat with Harmony until you have 4+ memories- Continue →
/create/vibe?sessionId=... - Click Generate My Song → creates song + generates 3 lyric variants (charges 1 credit)
- Studio placeholder
/create/studio?songId=...:- select a variant
- edit lyrics
- save
- (optional) enqueue audio generation
- Public gift page
/s/[slug]:- locked/unlocked depends on
drop_at - plays are tracked via
POST /api/public/songs/:slug/play
- locked/unlocked depends on
- Confirm Clerk JWT template is named
supabase - Confirm it includes
user_idclaim - Confirm it’s signed using Supabase JWT secret (HS256)
- Confirm your Clerk user has
public_metadata.supabase_user_idset (webhook must run at least once)
- Ensure
public.songs+public.activityare in thesupabase_realtimepublication - Ensure Clerk token is being passed to Realtime (the app calls
client.realtime.setAuth(token))
- Confirm
UPSTASH_REDIS_URLis set - Confirm the worker is running:
pnpm -C worker dev