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