πŸ—žοΈ Around Town

pnpm + Turborepo newsletter platform. Four town sites, one shared core, CRM-backed subscribe / archive / advertise, deployed from GitHub Actions to Vercel.

Overview Features Operations playbook Flow Tech Roadmap Test results πŸ§ͺ Pair test plan

Overview

LiveAgent id still aroundsummerlindev

Repomicrospaces/aroundtown (renamed from aroundsummerlin; old URLs redirect)
LocalC:/Users/AI/Repos/aroundtown
Owner agentaroundsummerlindev (folder/repo renamed; agent id not)
CRMSibling CRM app via REST proxies (CRM_API_URL, CRM_TENANT_ID, CRM_SITE_ID per app)
AppDomainVercel projectRoot directory
aroundsummerlinaroundsummerlin.comaroundsummerlinapps/aroundsummerlin
aroundhendersonaroundhenderson.comaroundhendersonapps/aroundhenderson
aroundcarsoncityaroundcarsoncity.comaroundcarsoncityapps/aroundcarsoncity
aroundpahrumparoundpahrump.comaroundpahrumpapps/aroundpahrump

App features

Operations playbook

The weekly run book β€” how issues get created, sent, and paid for. All steps verified live September 7–8, 2026.

1 Β· Create the weekly newsletter

  1. Research β€” scan local sources (summerlin.com events calendar, Review-Journal, library district calendar, community calendars) for the week's stories.
  2. Write β€” 6–8 sections, flexible per week, each headed with its icon. Structure stories EDH Insider–style: The Gist + Why It Matters.
  3. Illustrate β€” generate one flat-vector editorial illustration per major section (no copyrighted photos), upload to the headquarters Pages host, embed via <img>.
  4. Create draft in CRM β€” 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).
  5. Review β€” CRM β†’ Newsletter page β†’ Review full issue expander shows the rendered body.
  6. Publish β€” flip status to 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.

2 Β· Send the newsletter (sender.net)

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.

3 Β· Sell, create, and bill ads

  1. Lead arrives β€” advertiser fills the Advertise section on the site β†’ POST /api/advertise β†’ CRM advertisers record with status lead (tested: 201 Created, lead visible instantly).
  2. Qualify β€” agree on package (e.g. Founding Local β€” sidebar spot + mid-issue mention, $299/mo), flip the record to active with the package name.
  3. Bill β€” create an 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.
  4. Create the ad β€” the agent writes the spotlight copy in the newsletter's voice and generates matching illustration art from the CRM record (or hosts advertiser-supplied creative). Sponsors are embedded mid-issue after the Lead and mid-calendar, plus a flowing sidebar-styled spot.
  5. Approval β€” sponsor blocks appear in the issue review before publishing; Mike signs off there.

Flow

Build

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)

Runtime request

  1. Browser hits apex domain (Vercel project for that town).
  2. HTML/CSS/JS served from dist/.
  3. Forms/API calls go to same origin /api/* (serverless).
  4. Handlers use @townnews/core factories β†’ CRM REST with env CRM_API_URL / tenant / site.
  5. CRM down β†’ 502 is expected and acceptable in local/dev verification.

Deploy

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.

Domain gotcha (2026-09-04): CLI deploys have no git branch metadata. If a custom domain is attached with gitBranch: main, apex returns DEPLOYMENT_NOT_FOUND while *.vercel.app still works. Leave gitBranch unset on these domains.

Tech

Monorepopnpm 9.15.0 workspaces + Turbo 2.x. Root package name aroundtown.
Appsapps/aroundsummerlin, aroundhenderson, aroundcarsoncity, aroundpahrump
Sharedpackages/newsletter-core (@townnews/core) β€” templates, css/js, CRM lib, handler factories, server.mjs
Per-app configtown.config.json (siteName, tagline, city, state, domain, port, …)
Vercelframework: null, installCommand: cd ../.. && pnpm install --frozen-lockfile, buildCommand: node build.mjs, outputDirectory: dist
CI.github/workflows/vercel-deploy.yml matrix of four apps
Localpnpm 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/

πŸ—‚οΈ Roadmap

Live roadmap, managed as GitHub issues (aroundtown issues). Add or close items on GitHub β€” β˜‘ = done, close as β€œnot planned” to remove.

Test results

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

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)