Participants sign up, build their wishlist, and the organiser runs a draw that respects exclusions, sets a budget, avoids repeating past pairings, and delivers results via a reveal page, WhatsApp, email or push. Schedule it for later, reveal the pairings on a date β or keep them secret forever.
Rewritten from the ground up: from an Express prototype β Next.js 16 Β· React 19 Β· Better Auth Β· Postgres/Drizzle. Dockerised, deploy-ready for Coolify. π³
| Feature | What it does | |
|---|---|---|
| π | Accounts | Email/password login (Better Auth). The first user (or ADMIN_EMAIL) becomes admin automatically. |
| π | Wishlists | Each participant lists what they'd like to receive β their santa sees it all. |
| π« | Exclusions | Forbidden pairs (couples, siblings) never draw each other. |
| π° | Budget | Optional per-draw budget, shown on the reveal page. |
| π°οΈ | History | Past draws are used to avoid repeating last year's pairing β and each member sees their own match history on their dashboard. |
| π² | Robust algorithm | Backtracking derangement β respects constraints, or fails with a clear error. |
| π | Self-draw toggle | Optional per-draw switch to allow being matched to yourself (off by default). |
| β° | Schedule a draw | Pick a date/time β the draw runs and delivers itself automatically (in-process scheduler). |
| π | Reveal pairings | Choose a date when the full giverβreceiver list goes public (admin history + everyone's dashboard), or never. |
| π¬ | Pick your delivery | Per draw: reveal page, WhatsApp + link, or WhatsApp direct. |
| βοΈ | Email (optional) | Resend integration β emails the match to participants with a known account email. |
| π | Push (optional) | Installable PWA with Web Push notifications when a match is ready. |
| π | Account linking | Manually-added participants can later be linked to a real user account. |
| π | Site metadata | Admin "Technical" tab edits OpenGraph title, description, icon and link-preview banner at runtime. |
| π³οΈ | Phone with flags | Country dial-code picker with SVG flags + free-typed codes; stored WhatsApp-style. |
| π Reveal |
Private reveal links, with a per-link view limit. |
| π¬ WA + Link |
WhatsApp message containing the reveal link. |
| π
WA Direct |
WhatsApp message with the match's name in the template. |
| Layer | Choice |
|---|---|
| πΌοΈ Framework | Next.js 16 (App Router) Β· React 19 |
| π Auth | Better Auth (+ admin plugin) |
| ποΈ Database | Postgres via Drizzle ORM (pg) |
| π¨ UI | Tailwind CSS v4 Β· shadcn/ui Β· react-day-picker Β· flag-icons |
| π² Messaging | Meta WhatsApp Cloud API Β· Resend (email) Β· Web Push (PWA) |
| β° Scheduling | In-process poller via instrumentation.ts |
| π³ Deploy | Docker Β· docker-compose Β· Coolify-ready |
Brings up the app and Postgres together. Migrations run automatically on start.
# 1οΈβ£ Configure
cp .env.example .env
# β’ BETTER_AUTH_SECRET β openssl rand -base64 32
# β’ ADMIN_EMAIL β the email you'll sign up with (becomes admin)
# β’ POSTGRES_* β credentials for the bundled database
# β’ WA_* β (optional) only for WhatsApp delivery
# 2οΈβ£ Build + run
docker compose up -d --build # π http://localhost:3000# 1οΈβ£ Install
npm install
# 2οΈβ£ Configure (set DATABASE_URL to a running Postgres)
cp .env.example .env
# 3οΈβ£ Apply migrations
npm run db:migrate
# 4οΈβ£ Run
npm run dev # π http://localhost:3000π‘ Need a quick Postgres for local dev?
docker compose up -d dbstarts just the database.
π‘ Sign up with
ADMIN_EMAILto get the admin role, then open /admin to add participants, set exclusions, choose delivery, and run the draw.
This repo ships a Dockerfile + docker-compose.yml, so Coolify can deploy it
as a Docker Compose resource out of the box.
- New Resource β Docker Compose, point it at this repo.
- Set the environment variables (Coolify reads them into the compose file):
BETTER_AUTH_SECRETβ a long random stringBETTER_AUTH_URL/NEXT_PUBLIC_APP_URLβ your public URL (e.g.https://santa.example.com)ADMIN_EMAILβ the account to auto-promote to adminPOSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBWA_*β optional, only for WhatsApp deliveryRESEND_*β optional, for email deliveryVAPID_*/NEXT_PUBLIC_VAPID_PUBLIC_KEYβ optional, for push notifications
- Deploy. π The bundled Postgres persists in the
pgdatavolume and migrations run automatically on every container start.
βΉοΈ
NEXT_PUBLIC_APP_URLis baked into the client at build time β set it before the image is built so reveal links use the right domain.
βΉοΈ Prefer Coolify's managed Postgres instead of the bundled one? Remove the
dbservice from the compose file and setDATABASE_URLto the managed instance's connection string.
For wa_link / wa_direct you need a Meta WhatsApp Business account and an
approved template with two body parameters:
| Param | Content |
|---|---|
{{1}} |
The giver's name |
{{2}} |
The match's name (wa_direct) or the reveal URL (wa_link) |
Set WA_API_TOKEN, WA_PHONE_NUMBER_ID, WA_TEMPLATE_NAME,
WA_TEMPLATE_LANGUAGE in .env. Reveal-page delivery needs none of this. β
Powered by Resend. When configured, draw results are also emailed to participants whose account email is known.
RESEND_API_KEY="re_..."
RESEND_FROM="Secret Santa <santa@your.domain>" # verified senderLeave unset to disable email entirely. The admin Integrations card shows whether it's enabled.
The app is an installable PWA with Web Push. Generate a VAPID key pair once:
npx web-push generate-vapid-keysVAPID_PUBLIC_KEY="..."
NEXT_PUBLIC_VAPID_PUBLIC_KEY="..." # same value, inlined into the client
VAPID_PRIVATE_KEY="..."
VAPID_SUBJECT="mailto:you@example.com"βΉοΈ
NEXT_PUBLIC_VAPID_PUBLIC_KEYis baked in at build time (it's a Dockerfile build arg). Participants enable notifications from their dashboard.
| Script | What it does |
|---|---|
npm run dev |
π₯ Development server |
npm run build |
π¦ Production build |
npm test |
π§ͺ Draw-algorithm unit tests |
npm run db:generate |
𧬠Generate a Drizzle migration |
npm run db:migrate |
β¬οΈ Apply migrations |
npm run db:studio |
π Open Drizzle Studio |
app/ π§ routes (auth, dashboard, admin, reveal, api, manifest)
components/ π§© UI (shadcn + components)
lib/ βοΈ auth Β· db/schema Β· draw Β· draw-runner Β· scheduler
whatsapp Β· email Β· push Β· pairings Β· settings Β· phone
instrumentation.ts β° boots the in-process draw scheduler
public/ πΌοΈ PWA service worker + app icon
drizzle/ ποΈ SQL migrations
scripts/ π database migration runner
Dockerfile π³ standalone production image
docker-compose.yml π³ app + Postgres (Coolify-ready)
| Branch | What it is |
|---|---|
| π’ v2 | This rewrite (Next.js Β· Better Auth Β· Drizzle) β current branch |
| π‘ v1 | The original Express prototype (static HTML + JSON) |
Pull requests welcome! If you'd like to enrich the project, open an issue or a
PR. Any Ko-Fis given would be greatly welcome to allow me to continue to mantain this project development. If you have any questions or issues, feel free to contact me on Discord (prefereble) or via chat in https://chung-jf.me. My Discord nickname is Rodaviva. π
MIT β see LICENSE.