pnpm + Turborepo newsletter platform. Four town sites, one shared core, CRM-backed subscribe / archive / advertise, deployed from GitHub Actions to Vercel.
LiveAgent id still aroundsummerlindev
| Repo | microspaces/aroundtown (renamed from aroundsummerlin; old URLs redirect) |
|---|---|
| Local | C:/Users/AI/Repos/aroundtown |
| Owner agent | aroundsummerlindev (folder/repo renamed; agent id not) |
| CRM | Sibling CRM app via REST proxies (CRM_API_URL, CRM_TENANT_ID, CRM_SITE_ID per app) |
| App | Domain | Vercel project | Root directory |
|---|---|---|---|
| aroundsummerlin | aroundsummerlin.com | aroundsummerlin | apps/aroundsummerlin |
| aroundhenderson | aroundhenderson.com | aroundhenderson | apps/aroundhenderson |
| aroundcarsoncity | aroundcarsoncity.com | aroundcarsoncity | apps/aroundcarsoncity |
| aroundpahrump | aroundpahrump.com | aroundpahrump | apps/aroundpahrump |
town.config.json (site name, city/state, tagline), weekly date, subscribe form, past-issues search/archive, advertise CTA, privacy/terms footer./issue.html?slug=β¦ fetches one published issue (cover, volume/issue, body HTML).privacy.html / terms.html tokenized with the town name and domain.POST /api/subscribe β CRM contacts (201 / 400 / 502).POST /api/advertise β CRM advertiser leads.GET /api/issues and GET /api/issues/[slug] proxy published issues from CRM.GA_MEASUREMENT_ID is set.The weekly run book β how issues get created, sent, and paid for. All steps verified live September 7β8, 2026.
<img>.POST /api/newsletter-issues with title, slug, summary, bodyHtml, volume/issueNumber, status: "draft". Drafts are invisible to the public site (verified: 404 + not in archive).published (button in CRM). Issue appears instantly in the site archive and renders at /issue.html?slug=β¦.Section menu (rotate 6β8 per issue): ποΈ Lead Β· ποΈ Growth & Development Β· π½οΈ Dining Β· βΎ Sports Β· π Culture & Arts Β· π‘ Real Estate Β· ποΈ Business Spotlight Β· π¨βπ©βπ§ Family & Kids Β· π Outdoors Β· πΌ Business News Β· βοΈ Reader Mail Β· βοΈ Week Ahead Β· π Mark Your Calendar Β· π Every Week staples Β· π Seasonal notes. Always close with the calendar + recurring staples.
After publishing, the issue goes to all subscribers via sender.net using scripts/sender-newsletter.ps1 (in the aroundsummerlindev workspace):
# One-time per town setup (dashboard):
# verify sending domain (SPF/DKIM records β Vercel DNS),
# create a group per town, generate API token (masked entry / .env.sender)
# Weekly send:
pwsh scripts/sender-newsletter.ps1 sync -Site <siteId> -GroupId <groupId> # CRM subscribers β sender.net (excludes test contacts)
pwsh scripts/sender-newsletter.ps1 send -IssueId <id> -Site <siteId> # build email HTML + POST campaign
# Both support -DryRun; `build` writes the email HTML for inspection first.
The email renders cover image β title β summary β full body with illustrations, plus an archive link and unsubscribe slot. Subscribers added later are picked up by the next sync. Status: pipeline built + dry-run verified; awaiting sender.net API token to go live.
POST /api/advertise β CRM advertisers record with status lead (tested: 201 Created, lead visible instantly).active with the package name.advertiser-payments record (amountCents, description, method: "manual"|"stripe", status pending). Manual: mark paid when collected. Stripe: POST /api/advertiser-payments/checkout returns a payment link; the checkout.session.completed webhook auto-marks it paid. Stripe needs STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET on the crm project β pending.town.config.json + packages/newsletter-core templates
β
βΌ
apps//build.mjs β dist/ (no leftover {{TOKENS}})
β
βΌ
Vercel static outputDirectory = dist
Vercel functions = apps//api/* (re-export core handlers)
dist/./api/* (serverless).@townnews/core factories β CRM REST with env CRM_API_URL / tenant / site.Push to main (paths apps/**, packages/**, or the workflow file) or workflow_dispatch. Matrix job per app: vercel link --project $APP, PATCH rootDirectory to apps/$APP, vercel deploy --prod. Full monorepo is uploaded so workspace:* resolves. Not Vercel git integration.
gitBranch: main, apex returns DEPLOYMENT_NOT_FOUND while *.vercel.app still works. Leave gitBranch unset on these domains.| Monorepo | pnpm 9.15.0 workspaces + Turbo 2.x. Root package name aroundtown. |
|---|---|
| Apps | apps/aroundsummerlin, aroundhenderson, aroundcarsoncity, aroundpahrump |
| Shared | packages/newsletter-core (@townnews/core) β templates, css/js, CRM lib, handler factories, server.mjs |
| Per-app config | town.config.json (siteName, tagline, city, state, domain, port, β¦) |
| Vercel | framework: null, installCommand: cd ../.. && pnpm install --frozen-lockfile, buildCommand: node build.mjs, outputDirectory: dist |
| CI | .github/workflows/vercel-deploy.yml matrix of four apps |
| Local | pnpm install Β· pnpm build Β· pnpm dev:aroundsummerlin (and siblings) β Git Bash on Windows |
apps//
town.config.json
build.mjs
api/ # subscribe, advertise, issues, issues/[slug]
vercel.json
.env.example # CRM_* and optional GA_MEASUREMENT_ID
packages/newsletter-core/
Live roadmap, managed as GitHub issues (aroundtown issues). Add or close items on GitHub β β = done, close as βnot plannedβ to remove.
Canonical checklist: docs/TEST_PLAN.md (static UI, subscribe, advertise, issues API, CRM, deploy, four-town branding).
π§ͺ Pair test plan β projects/aroundtown-test-plan.html: the live two-tester walkthrough (customer / admin / newsletter send / advertiser / billing) we execute together, with the results-and-issue log filled in as we go.
2026-09-04 β rename + four-domain cutover
pnpm build β 4/4 apps, 0 unresolved {{TOKEN}}s in built index.html.package.json was missing turbo / packageManager / per-app dev scripts).gitBranch on the three new domains:
Manual UI / CRM happy-path boxes in TEST_PLAN are not fully ticked yet β treat this cutover as deploy + branding verification, not a full QA pass.
2026-09-07/08 β operations verified live (Summerlin)
/issue.html?slug=latin-heritage-parade-september-summerlin.