
# [Brokerage Name] · Listings

A brokerage listings site. MLS feed in, individual listing pages out, agent contact form on every listing, saved searches via email.

## Source of truth
The MLS is the source of truth for listings. A scheduled job pulls the RETS/RESO Web API feed every 15 minutes and writes to Postgres. Listing pages are statically regenerated from the DB. Agent profiles are CMS-managed (Sanity).

## Tech stack
Next.js 15 (App Router) + React + Tailwind v4. Postgres (Vercel Postgres) for listings + saved-search subscribers. Sanity for agent profiles + brokerage content. Mapbox for the listing map. Cloudinary for listing photo optimization. Resend for the daily saved-search emails. Vercel cron for the MLS pull.

## Deploy
- Site: `git push` -> Vercel auto-deploys
- MLS sync: Vercel cron hits `/api/cron/mls-sync` every 15 minutes
- Saved search emails: daily cron hits `/api/cron/saved-search-digest`

## File map
- `app/page.tsx` home: featured listings, search bar, agent grid
- `app/listings/page.tsx` search results with filters
- `app/listings/[mlsId]/page.tsx` individual listing
- `app/agents/[slug]/page.tsx` agent profile + their active listings
- `app/saved-search/page.tsx` user manages their saved searches
- `app/api/cron/mls-sync/route.ts` pull feed, upsert to Postgres
- `app/api/cron/saved-search-digest/route.ts` daily email
- `app/api/lead/route.ts` lead form: assigns to the listing's agent
- `lib/mls.ts` RESO Web API client (auth, pagination, parsing)
- `lib/round-robin.ts` lead routing when no agent attached

## .env keys
- `DATABASE_URL`
- `MLS_API_BASE_URL`, `MLS_API_USERNAME`, `MLS_API_PASSWORD` (RESO Web API)
- `SANITY_PROJECT_ID`, `SANITY_DATASET`, `SANITY_TOKEN`
- `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`
- `RESEND_API_KEY`, `RESEND_FROM_EMAIL`
- `MAPBOX_ACCESS_TOKEN`
- `CRON_SECRET` Vercel cron auth

## Hard rules
- MLS feed terms have rules. Listing photos can only be served from MLS-approved URLs (Cloudinary fetch + cache is usually fine; cold-stored copies are usually not). Read the feed agreement.
- Hide "off market", "expired", "pending" or "sold" from the public site unless explicitly opted in.
- Display the agent name AND brokerage name on every listing (MLS attribution rule).
- Lead form notifies the listing agent within 30 seconds. Real estate leads decay FAST.
- Saved searches send a daily digest, not realtime. Daily is the sweet spot for engagement.
- Comply with IDX rules for your MLS. Each MLS has slightly different display requirements.

## Recent significant changes
- 2026-05-02: Scaffolded. Locked: RESO Web API over RETS (RESO is the modern path), Sanity for agent CMS (non-dev edits), saved searches over realtime alerts (better retention).

## Next session: start here
1. Get MLS credentials. Most MLSs require a brokerage license + signed agreement.
2. Set up Postgres. Apply schema for `listings`, `agents`, `saved_searches`.
3. Run the MLS sync once manually. Confirm 50-200 listings ingest cleanly.
4. Build the listing detail page. Match an agent to it. Submit a real lead.
5. Confirm the agent receives the lead email within 30 seconds.
6. Set up Vercel cron, confirm it fires on schedule.
