Open-Source Cold Email & Warmup Platform
Warmbly는 메일함을 예열하고 캠페인 전송·답변 triage를 지원하는 오픈소스 콜드 이메일 플랫폼입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Nail store loyalty
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Loyalty check-in for Best Nails: an iPad kiosk where customers type their phone number on a numpad to check in, and a staff admin for customers, check-ins and rewards.
The program: every check-in earns 1 point · 10 points = 10% off · a birthday 10% off, once a year within 7 days of the birthday. Customers can redeem either at the kiosk after checking in, or staff can redeem them in the admin.
Gift vouchers: a customer buys prepaid credit (e.g. $100) and spends it over one or more visits. Staff create one under Gift vouchers (the customer pays at the till as usual): the app generates a code like BN48207138: the business prefix and 8 digits, the last one a check digit that catches most typos (Generate makes a new one), or staff type the number printed on a voucher card. BN is the business prefix, which an admin can change in Settings if they run several businesses. At each visit staff enter today's service amount. If it costs more than what's left, the app uses the rest and shows how much to take at the till. A use entered by mistake is put back with Adjust balance (admin). Vouchers are valid for 3 years (the legal minimum in Australia), and the dashboard shows the unspent credit.
Design: Claude Design canvas (kiosk screens, admin pages, design tokens).
Next.js 16 (App Router, Cache Components, Server Actions) · React 19 · Tailwind CSS v4 + shadcn/ui · Supabase (Postgres, Auth, RLS) · Zod · Vitest + pgTAP
Requires Node 20+, Docker Desktop (running) for local Supabase.
npm install
npx supabase start # Postgres + Auth in Docker; applies migrations and seed
npx supabase status -o env # copy API_URL / PUBLISHABLE_KEY / SECRET_KEY into .env.local (see .env.example)
npm run dev
[email protected] / bestnails-dev (admin, local only). Add staff accounts in Settings → Accounts| Piece | Where |
|---|---|
| Schema, RLS, check-in / redeem / adjust functions | supabase/migrations/ |
| Points rules | _award_check_in: 1 point per check-in, one per shop day (in settings.timezone) |
| Redeeming | kiosk_redeem / redeem_reward spend points on a reward; kiosk_redeem_birthday / redeem_birthday use the birthday discount (_birthday_status decides eligibility). Kiosk redemption needs a check-in in the last 12 hours |
| Phone numbers | lib/phone.ts: Australian numbers are stored as +61…; non-standard numbers are accepted with a warning, never blocked |
| Kiosk Server Actions (signed-in account, minimal data out) | app/kiosk/actions.ts |
| Kiosk UI (numpad, screens, idle reset) | components/kiosk/ |
| Admin Server Actions (staff session re-checked, RLS client) | app/admin/actions.ts |
| Admin reads | lib/admin-data.ts |
Points are an append-only ledger (point_transactions); customers.points_balance is a cached total that a trigger only lets the ledger functions change. Concurrent check-ins for the same customer serialise on a row lock, so a double tap can't count twice.
The kiosk never talks to Supabase from the browser. Its Server Actions use the service key only after checking that an account is signed in on the device, and they return just a first name, masked phone, points and visit count.
npm test # unit tests (phone rules, promotion scheduling)
npm run test:db # pgTAP: check-in rules, one check-in a day, permissions, redeem/adjust, gift vouchers, business rules, ledger consistency
npm run typecheck && npm run lint && npm run build
supabase/migrations/, then npx supabase migration up && npm run db:typesnpm run db:reset (this also signs everyone out)Create a Supabase project and run npx supabase link then npx supabase db push. Don't run seed.sql in production. Create the first admin once: add a user in the Supabase dashboard (Authentication → Users, with "Auto confirm"), then in the SQL editor run insert into public.staff (user_id, display_name, role) select id, 'Your name', 'admin' from auth.users where email = '[email protected]';. Everyone after that is added in Settings → Accounts. Deploy the app to Vercel with the three env vars. On the iPad, sign in, open /kiosk, "Add to Home Screen" (no address bar), and turn on Guided Access while a customer has it.
Known limits: "Forgot password?" emails need your own email (SMTP) set up in Supabase; until then an admin resets passwords in Settings → Accounts.