Back to samples
Pinnacle Roasters logo
Food & Beverage

Pinnacle Roasters

Order ahead — menu browsing, customization, loyalty rewards

4 min read

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

1. Clone and install

Bash
git clone https://github.com/pinnacle-samples/Pinnacle-Roasters
cd Pinnacle-Roasters
npm install

2. Configure environment

Bash
cp .env.example .env
env
PINNACLE_API_KEY=your_api_key_here
PINNACLE_AGENT_ID=your_agent_id_here
PINNACLE_SIGNING_SECRET=your_signing_secret_here
TEST_MODE=false
PORT=3000

3. Expose your webhook

Bash
ngrok http 3000

4. Connect the webhook

In the Webhooks dashboard:

  1. Add https://<your-tunnel-domain>/webhook
  2. Attach it to your RCS agent
  3. Copy the signing secret into PINNACLE_SIGNING_SECRET

5. Run it

Bash
npm run dev

Send 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

ActionWhat it does
showMainMenuLanding card with menu, loyalty, and events
sendMenuCategoriesTop-level menu categories
viewCategoryItems inside a chosen category
addToCart / viewCart / clearCartCart management
checkoutConfirms and clears the cart
viewLoyalty / redeemRewardLoyalty points + reward redemption
sendEventsUpcoming in-store events
showAppointmentTimes / confirmAppointmentRSVP slot picker

Customize the menu

Open data/menu.yml and append a new category. Categories and items are plain YAML — no code changes required:

YAML
- 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.jpg

sendMenuCategories 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:

YAML
rewards:
  - name: Caffeine Boost
    points: 100
    description: Grab any size espresso — your well-earned caffeine boost!
    image: https://yourcdn.com/coffee.jpg

Every 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=false and submit your agent for carrier approval
  • Replace the in-memory carts Map with Postgres or Redis
  • Wire the checkout flow to your real POS or Stripe

Resources

© 2026 Pinnacle Software Development, Inc.