Note: the visuals in this demo recording have since been refreshed with sharper brand assets. The conversation flow is identical to what you'll get from a fresh clone.
What's inside
- Browse menu categories — coffee, tea, pastries, food
- Customize drink size, milk, and add-ons
- Persistent cart across messages with running total
- Loyalty points earned on every order, redeemable for free items
- Order confirmation with pickup time and order number
A cafe ordering chatbot that runs entirely inside RCS. Customers browse the menu by category, customize items, build a cart, RSVP to in-store events, and earn loyalty points on every order — no app install required.
This guide gets you from a fresh clone to taking orders on your phone in under 10 minutes.
What you'll build
- A Pinnacle RCS agent that walks customers through a full ordering flow
- Business info, menu, and loyalty rewards driven from three editable YAML files
- An in-memory cart per customer with running totals and clear-cart support
- A loyalty rewards program with 1000 starter points so the redemption flow is demoable on day one
- An in-store events flow for RSVPing to workshops and tastings
Prerequisites
- Node.js 18+
- A Pinnacle account — sign up. Add an RCS test agent for development
- An API key and a webhook signing secret
1. Clone and install
git clone https://github.com/pinnacle-samples/Pinnacle-Roasters
cd Pinnacle-Roasters
npm install2. Configure environment
cp .env.example .envPINNACLE_API_KEY=your_api_key_here
PINNACLE_AGENT_ID=your_agent_id_here
PINNACLE_SIGNING_SECRET=your_signing_secret_here
TEST_MODE=false
PORT=30003. Expose your webhook
ngrok http 30004. Connect the webhook
In the Webhooks dashboard:
- Add
https://<your-tunnel-domain>/webhook - Attach it to your RCS agent
- Copy the signing secret into
PINNACLE_SIGNING_SECRET
5. Run it
npm run devSend MENU or START to your agent. You'll see the cafe landing card with Menu, Loyalty & Rewards, and Events buttons.
How the pieces fit together
Pinnacle-Roasters/
├── server.ts # Express bootstrap
├── router.ts # /webhook POST — verifies + dispatches
├── data/
│ ├── business.yml # Brand name, tagline, address, phone, image
│ ├── menu.yml # Categories + items, prices, descriptions, images
│ └── loyalty.yml # Reward tiers + points cost
└── lib/
├── rcsClient.ts # PinnacleClient instance
├── baseAgent.ts # Shared send + typing helpers
├── typing.ts # Fire-and-forget typing indicator
├── agent.ts # Agent — every action handler lives here
└── brand/
├── menu.ts # YAML loader (business, menu, loyaltyRewards)
└── types.ts # BusinessInfo, MenuCategory, MenuItem, LoyaltyReward
All brand-specific content lives in data/*.yml. The loader in lib/brand/menu.ts parses the YAML at startup so a non-engineer can edit the menu without touching code.
Action handlers
| Action | What it does |
|---|---|
showMainMenu | Landing card with menu, loyalty, and events |
sendMenuCategories | Top-level menu categories |
viewCategory | Items inside a chosen category |
addToCart / viewCart / clearCart | Cart management |
checkout | Confirms and clears the cart |
viewLoyalty / redeemReward | Loyalty points + reward redemption |
sendEvents | Upcoming in-store events |
showAppointmentTimes / confirmAppointment | RSVP slot picker |
Customize the menu
Open data/menu.yml and append a new category. Categories and items are plain YAML — no code changes required:
- name: 🍂 Seasonal
description: Limited-time fall favorites.
image: https://yourcdn.com/seasonal.jpg
items:
- name: Pumpkin Spice Latte
price: $5.50
description: Espresso, pumpkin spice syrup, steamed milk, whipped cream
image: https://yourcdn.com/psl.jpgsendMenuCategories iterates over whatever's in menu.yml, so the new category shows up on the next message — restart the server to reload the YAML.
Loyalty program
Rewards live in data/loyalty.yml. Each entry has a name, points cost, description, and image:
rewards:
- name: Caffeine Boost
points: 100
description: Grab any size espresso — your well-earned caffeine boost!
image: https://yourcdn.com/coffee.jpgEvery new customer starts with 1000 points (configured as defaultPoints in lib/agent.ts) so you can demo the redemption flow on day one.
Customize the brand
Edit data/business.yml to rename the cafe, swap the logo, or update the address — the landing card rebuilds from these fields automatically.
Going to production
- Set
TEST_MODE=falseand submit your agent for carrier approval - Replace the in-memory
cartsMap with Postgres or Redis - Wire the checkout flow to your real POS or Stripe
Resources
- Repo: github.com/pinnacle-samples/Pinnacle-Roasters
- Docs: docs.pinnacle.sh
- Dashboard: app.pinnacle.sh/dashboard
- Support: founders@trypinnacle.app

