Built with
Everything else
Jina and pgvector
Search by meaning, so "yellow cast iron pot" finds Maya's Le Creuset even though she never wrote "cast iron".
- Embedding.
lib/server/embeddings.tscallsPOST https://api.jina.ai/v1/embeddingswith 1024 dimensions, normalized. A listing is embedded as aretrieval.passage(title, one-liner, category, details and description) when it's published and whenever its words change (refreshEmbedding); a search as aretrieval.query. - Storing.
listing.embeddingisvector(1024)with an HNSW cosine index. Migration0000_enable-pgvectorswitches the extension on. - Searching.
searchListingsinlib/server/market.tsruns two rankings side by side, 200 candidates each: Postgres full text (websearch_to_tsquery, ranked byts_rank_cd, with a boost when the shop name matches) and pgvector cosine distance, ignoring anything under 0.3 similarity so "boots" doesn't return mugs. They're merged with reciprocal rank fusion: each ranking adds1 / (60 + rank). - Without it. No
JINA_EMBEDDING_MODEL_KEY, or a failed call, and search quietly uses text alone.
JINA_EMBEDDING_MODEL_KEY=jina_…
JINA_EMBEDDING_MODEL=jina-embeddings-v5-text-small # the defaultCloudflare R2
Photos and videos. lib/server/files.ts signs S3 requests with aws4fetch and stores uploads at uploads/{owner}/{id}.{ext}. Photos can be up to 10 MB, videos 40 MB. The rest of the app only sees file ids and /api/files/{id}, which redirects (308) to R2_PUBLIC_URL when there is one or streams from the bucket, cached forever since files never change.
Without R2 the bytes are kept in Postgres (file.data), which is fine locally. Env: R2_URL, R2_ACCESS_KEY, R2_SECRET_ACCESS_KEY, R2_BUCKET (default resell-store), R2_PUBLIC_URL (optional).
Resend
sendEmail() in lib/server/email.ts renders a React Email template to HTML and plain text and posts it to https://api.resend.com/emails. Without RESEND_API_KEY the text is logged to the console instead, and in development the sign-in link shows on the sign-in page. Notifications to example.com addresses are logged, never sent (lib/server/notify.ts), so seeded accounts stay quiet. Env: RESEND_API_KEY, EMAIL_FROM, and SUPPORT_EMAIL for problems that need a person.
Better Auth
lib/server/auth.ts, with the Drizzle adapter and the handler at/api/auth/[...all]. Sign-in is an email link (15 minutes). Sessions last 30 days.- Stores live on subdomains, so in production the session cookie is set on
.resell.storeand every store reads it. Browsers won't share a cookie across*.localhost, so in development a one-time token hands the same session to each store host (see Domains and routing). - Pages check auth themselves with
requireUser()fromlib/server/session.ts, which sends signed-out people to/welcome. Env:BETTER_AUTH_SECRET(required),BETTER_AUTH_URL.
React Email
packages/email holds the templates as React components with a shared theme, so emails look like the app. There are 10: sign-in, offer received, offer update, sold, shipped, order placed, paid out, new from a shop you follow, the evening agent summary, and a general notice. Preview them with pnpm email (port 5690).
MCP TypeScript SDK
packages/mcp builds both MCP servers on @modelcontextprotocol/server 2.3.0: McpServer and registerTool for the tools, createMcpHandler for the hosted HTTP endpoint, serveStdio for the local command, and elicitation for "Ask me first". How the package fits together is on API, docs and MCP.
Drizzle
packages/db (@repo/db) has the schema (src/schema.ts, 33 tables) and the SQL migrations (packages/db/drizzle, 0000 to 0015). Render runs pnpm db:migrate before each deploy.
| Command | Does |
|---|---|
pnpm db:generate | Writes a migration from schema changes |
pnpm db:migrate | Applies migrations |
pnpm db:studio | Opens Drizzle Studio |
Import query helpers (eq, and, sql…) from @repo/db, never from drizzle-orm directly, so the app and the schema share one copy of Drizzle.
Base UI and Tailwind
packages/ui (@repo/ui) is the component kit: buttons, chips, fields, item and seller cards, the tab bar, dialogs, drawers, popovers, toasts, tooltips and the icon set. Interactive pieces are built on @base-ui/react for accessible behaviour, styled with Tailwind CSS v4 from the tokens in src/styles.css. Components import as @repo/ui/button, icons as @repo/ui/icons, and helpers like cn from @repo/ui/lib/utils.