Open-Source Cold Email & Warmup Platform
Warmbly 是開源的冷郵件平台:溫暖您的收件箱、發送活動、並對每個回覆進行分流。可在我們的雲端運行或自行託管整個系統。
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
TapMart is a live marketplace for attention: a board where links — shops,
projects, creators, apps, launches, anything with a URL — compete to be
discovered. Add your link, add credit, get seen. One link owns The Spot for
sixty seconds at a time; the three biggest backers of the day hold Top 3;
everyone else ranks on The Board; up to a hundred live links stream through
The Bar at the bottom of every page. Creators share the site and Earn
from the qualified traffic they send.
The public product is deliberately simple. The machinery under it is not.檢視完整 README ↗What's getting clicked right now?
Official production site: https://tapmart.live
TapMart is a live marketplace for attention: a board where links — shops, projects, creators, apps, launches, anything with a URL — compete to be discovered. Add your link, add credit, get seen. One link owns The Spot for sixty seconds at a time; the three biggest backers of the day hold Top 3; everyone else ranks on The Board; up to a hundred live links stream through The Bar at the bottom of every page. Creators share the site and Earn from the qualified traffic they send.
The public product is deliberately simple. The machinery under it is not.
The name, tagline, domain, and metadata all live in
src/config/site.ts— brand and domain changes are a one-file edit. Internal identifiers (cookie names, database names, the repository/Vercel project namemarketing-app) intentionally keep their old spellings; renaming them would risk sessions and deployments for zero public benefit.
apply_stripe_topup claims the Checkout session id with a
unique constraint).record_click) that locks the
wallet and placement, re-checks the balance, and refuses to go below zero
under any concurrency.credit_ledger with balance
before/after. The wallet's cached total is a performance convenience; the
ledger is the record.Board rank is decided by credit added to the Board during the current daily round — never by what remains. These are two separate columns:
board_round_entries.score_cents | placements.remaining_credit_cents | |
|---|---|---|
| Add $50 to Board | 5000 | 5000 |
| 980 opens at 5¢ | 5000 (unchanged) | 100 |
| Add $20 more | 7000 | 2100 |
| Daily reset | 0 (new round) | 2100 (money survives) |
A #1 link with one cent of remaining credit is still #1. At exactly $0
remaining the placement goes inactive and off the board, its score preserved in
round history. Ties after a reset break by previous-round rank, then activation
time, then id. Qualified clicks never touch score_cents — this is
enforced in the SQL and pinned by tests.
Every paid open leaves through /go/[placementId], which decides — server-side,
before redirecting — whether the click is billable:
Sec-Purpose, Purpose, X-moz honoured)Unqualified clicks are recorded with a rejection reason and redirect anyway — visitors are never punished, they are simply not billed. Visitor identity is a first-party anonymous cookie plus a keyed HMAC of the IP; raw IPs are never stored and there is no fingerprinting.
/s/[code] opens a referral session for the visitor (last touch wins, 30-day
window) and forwards them on. When a referred visitor later makes a qualified
open, the creator earns the configured commission (default 1¢ of the 5¢ click)
— exactly once per click, enforced by a unique constraint on the click event.
Earnings sit pending through a fraud hold (default 7 days), then become
available; payouts are requested manually and reviewed by an admin. No payout
rail is wired in V1 (the architecture leaves room for Stripe Connect).
Application queries go straight to Postgres over the pooled connection string
(the money logic lives in SQL functions that need real transactions). RLS is
enabled on every table regardless, so nothing reachable with a user JWT can see
another user's balances — and public views (public_board, public_bar,
public_spot) never expose a placement's remaining credit.
# 1. Postgres (any local instance works)
createdb untitled # role/password in .env.example
cp .env.example .env.local # fill DATABASE_URL, AUTH_SECRET, CLICK_HASH_SECRET
# set AUTH_DEV_MODE=true to sign in without email
# 2. Schema + seed
npm install
npm run db:reset # applies supabase/local + supabase/migrations
npm run seed # 28 polished dev links, three test accounts
# 3. Run
npm run dev
Dev accounts after seeding (password password123 signs any email in while
AUTH_DEV_MODE=true):
[email protected] (owns every seeded link) · [email protected]
(referral earnings) · [email protected] (admin at /admin).
The seed script refuses to run against a non-local database unless
SEED_ALLOW_REMOTE=true — development data must never masquerade as
production activity.
npm test # 56 tests; needs local Postgres (creates untitled_test)
npm run typecheck
npm run lint
npm run build
The suite pins the §64–§73 product requirements: board score never falling, top-ups raising score, reset preserving money, exhaustion delisting, duplicate suppression, creator paying exactly once, concurrent clicks never overspending, Stripe webhook replays crediting exactly once, spot scheduling spread, and bar capacity + queue promotion.
Create a project at supabase.com.
Apply migrations: supabase link --project-ref <ref> then supabase db push
(or run the files in supabase/migrations/ in order via the SQL editor —
skip supabase/local/, which is the local-only auth shim).
Auth → enable Email provider (password sign-in). Storage → create a public
bucket link-images.
Copy into your deployment env: NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY (server-only),
and set DATABASE_URL to the connection pooler URI.
Auth → URL Configuration must know every domain the app runs on, or verification/reset emails will land on dead links:
https://tapmart.livehttps://tapmart.live/** — plus
https://<project>.vercel.app/** while that alias is still in use, and
http://localhost:3000/** for local development.The app builds those links from SITE_URL in src/config/site.ts
(overridable per-environment with NEXT_PUBLIC_SITE_URL; local dev
automatically uses http://localhost:3000).
STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY.https://<domain>/api/stripe/webhook subscribed to
checkout.session.completed; its signing secret → STRIPE_WEBHOOK_SECRET.stripe listen --forward-to localhost:3000/api/stripe/webhook.Money flow: Checkout session → webhook verifies signature → apply_stripe_topup
(idempotent) → optional immediate allocation from the session's metadata.
marketing-app — only the public domain matters.).env.example (leave AUTH_DEV_MODE unset).vercel.json already schedules /api/cron every 10 minutes — it closes
expired rounds, schedules the Spot day, resyncs the Bar, and releases
creator earnings past their hold. Set CRON_SECRET to protect it.tapmart.live (and www.tapmart.live) under
Project → Settings → Domains, with the apex as the primary domain so www
redirects to it. Point DNS at Vercel (A 76.76.21.21 for the apex, CNAME
cname.vercel-dns.com for www — the Domains screen shows the current
records). Preview deployments keep their generated URLs; production
canonical URLs, share links, and auth emails all use https://tapmart.live
via src/config/site.ts.supabase/migrations/ schema, settings, money functions, click/spot/bar, RLS
supabase/local/ auth-shim so migrations run on plain Postgres (never deploy)
src/config/site.ts name, tagline, domain, metadata — the brand point
src/lib/ db pool, auth, settings, validation, rate limit, click prequal
src/app/ routes: / board add dashboard earn admin l/[slug] go s api
src/components/ Spot, TopThree, Board, Bar, countdowns, header/footer
scripts/ db-reset, seed, browser verification sweep
tests/ money + placement + validation suites