# We Built a Volleyball Coach App With 4 AI Agents Across WhatsApp, Voice, and Email in 10 Minutes.
> A walkthrough of building the full backend of an AI-native app with Struere. One frontend, four agents on four channels: in-app chat, WhatsApp, voice phone calls, and weekly email digests. All sharing the same database. Built with prompts to Claude Code in about ten minutes.

Published: 2026-05-10
Tags: tutorial, agents, whatsapp, voice, email, claude-code, video, getting-started


<YouTube id="tS0w_n_gJvI" />

## What You Need

- A Struere account (free tier works)
- Claude Code, Cursor, or any coding agent
- A Twilio account for voice (optional, only for the voice agent)
- A WhatsApp-connected number through Meta Business Manager (optional, only for the WhatsApp agent)
- About 10 minutes

# A Coach Who Can't Be in Five Places at Once

Coach has a roster of volleyball players. Before every match, he picks a callup, the squad of players he wants for that game. He messages each player to confirm availability. A few confirm right away. Most don't reply for hours. When someone cancels at the last minute, he scrambles through his contacts to find a replacement, calling four or five people before someone picks up. After the match, he updates his stats spreadsheet. On Sunday night, he tries to remember what happened all week.

The problem has four faces. Players live on WhatsApp. Replacements answer the phone. The coach wants stats inside the app he already uses. On Sunday, he wants a single email summarizing the week.

We built that. Four agents, four channels, one shared database. Built in about ten minutes with prompts to Claude Code. This post walks through every step from the video above.

# What We're Building

The end result is **Sportistics**, a volleyball coach app powered by four Struere agents:

| Agent | Channel | What It Does |
|-------|---------|--------------|
| **Coach Assistant** | In-app chat widget | Answers stats and roster questions over text inside the app |
| **WhatsApp Callup** | WhatsApp | Asks each player about availability and saves their answer to the database |
| **Voice Replacement** | Twilio voice call | Phones a candidate when a player cancels, asks if they can play, and updates the callup |
| **Weekly Digest** | Email | Sends the coach a summary every Sunday: matches, top scorers, training load |

All four read and write to the same entities: `Player`, `Match`, `Callup`, `Event`, `TrainingSession`. The agents talk to each other when needed. When someone cancels on WhatsApp, the WhatsApp agent triggers the voice agent to find a replacement.

# Two Ways to Build: Studio vs Local CLI

Struere gives you two paths.

**Studio.** Open the dashboard, type what you need into a prompt box, and an in-platform agent builds everything for you. No local setup required. Good for first-time users and quick experiments.

**Local CLI.** Use your own coding agent (Claude Code, Cursor, Windsurf) to scaffold the project, write the agent definitions as TypeScript files, and sync to the cloud. You get version control, full editor support, and you don't burn Struere inference tokens because Claude does the heavy lifting on your existing subscription.

The video uses the local CLI. Every step below assumes you're working from your terminal.

# Step 0: Clone the Frontend Boilerplate

The boilerplate is a working volleyball coach frontend with mock data baked in. React, Vite, no backend.

```bash
git clone https://github.com/MarcoNaik/sportistics-example
cd sportistics-example
bun install
bun dev
```

Open `localhost:5173` and the app runs. You can browse players, matches, and the team roster. The data is hard-coded JSON in the frontend.

Starting from a working frontend saves you from burning Claude Code tokens generating React boilerplate. The agentic backend is where you want to spend tokens.

# Step 1: Run `bunx struere init`

The first prompt to Claude Code is short. It tells Claude to set up the local Struere project.

```
You are working in a fresh Sportistics project.

Run `bunx struere init` to download the CLI and authenticate with Struere.
Initialize the project so all boilerplate files end up locally on disk.
Then install the `struere-developer` skill so you understand how the
documentation and the SDK work.

When that's done, list the project structure and confirm everything is
synced to my Struere cloud deployment.
```

Claude Code does four things:

1. Downloads the Struere CLI tool
2. Authenticates with your Struere account (you approve in the browser)
3. Initializes a project: empty `agents/`, `entity-types/`, `tools/`, `triggers/` directories with the right tsconfig and `struere.json`
4. Installs the `struere-developer` skill from the Anthropic skills marketplace, so Claude Code knows the SDK reference, the CLI commands, and the platform conventions

After this step, the local Struere project exists but is empty. The frontend still runs on mock data.

# Step 2: Migrate Mock Data to Real Entities

The second prompt is bigger. It does two things at once: defines the database schema in Struere, and connects the existing frontend to it.

```
The frontend has mock data for players, matches, callups, training
sessions, and events. Mirror that into Struere as real entity types.

For each mock data type:
1. Create an `entity-types/<name>.ts` file with the defineData primitive
2. Match the field names and types to what the frontend already expects
3. Add appropriate search fields and display fields

Then run `bunx struere sync` to push the schema to the cloud.

Once the entities exist, replace the mock data in the frontend with
calls to the Struere SDK. Use an API key (create one with the CLI) and
load it from environment variables.

Read the latest documentation before writing any of this.
```

Claude Code creates the entity files, syncs them, generates an API key with `bunx struere keys create`, and rewires the frontend to use the SDK instead of the JSON files. When it's done, you reload the app and the table is empty: the cloud database is empty too. Add a player through the UI, refresh, and the player persists. Open the Struere dashboard and the same player shows up in the Data tab.

The frontend now talks to a real backend without you writing a line of backend code.

## Seeding Mock Data

For the rest of the build, you want enough data to test the agents against. Send a quick prompt:

```
Use the CLI to populate the database with realistic mock data:
20 players with phone numbers, 10 upcoming matches, callups for each
match, training sessions over the past month, and a few hundred events
distributed across recent matches.
```

The Struere CLI has a `data` subcommand that lets the agent query and create records directly. Within a minute, the database is populated and the frontend has something to show.

This is where the self-healing pattern shows up. When something looks off, ask Claude Code to query the database and check. "Why is this match showing the wrong score?" becomes a one-line prompt instead of a debugging session.

# Step 3: Build the Four Agents

The final prompt is the longest. It defines all four agents, their tools, their permissions, and the trigger that runs the weekly digest.

The structure of the prompt:

- A short overview of what we're building
- Per-agent specs: name, channel, system prompt direction, tools needed
- A list of about 10 custom tools the agents share (`set_availability`, `get_replacement_candidates`, `query_stats`, `make_voice_call`, `send_email`, etc.)
- Roles to scope what each agent can read and write
- A cron trigger for the weekly digest
- Instructions to wire up the in-app chat widget on the frontend's agent panel

You don't need this much detail. The level of specification in the video matches the demo we wanted. For your own use case, write the spec at the level you need. Claude Code handles the rest.

## What Gets Created

After Claude Code finishes, four agent files exist in `agents/`:

```
agents/
  coach-assistant.ts      → in-app chat widget
  whatsapp-callup.ts      → WhatsApp messaging
  voice-replacement.ts    → outbound phone calls
  weekly-digest.ts        → cron-triggered email
```

Each agent has a system prompt and a list of tools. The tools live in `tools/index.ts` and are shared across agents. Roles in `roles/` control what each agent can read. The WhatsApp agent sees its own player record and the matches it's been asked about, but not the full roster.

# Connecting WhatsApp and Twilio

While Claude Code builds the agents, you connect the two external integrations through the dashboard.

## WhatsApp

In the Struere dashboard, go to **Integrations → WhatsApp → Add number**. Follow the Meta Cloud API setup, link your WhatsApp Business account, and pick the number you want to connect. Assign the WhatsApp Callup agent to that number.

If you also want the Coach Assistant on WhatsApp, use a Struere router to send the coach's number to one agent and unknown numbers (players) to the other. Out of scope for this video, but it's a one-liner.

## Twilio

For voice, configure Twilio with your account SID, auth token, and a phone number, then assign that number to the Voice Replacement agent. The agent uses OpenAI Realtime under the hood. You get natural-sounding voice, sub-second response time, and tool calls work mid-conversation as they do in text agents.

# Testing the Agents

The Struere dashboard has a **Chat** view that shows every conversation across every channel. You can also test agents from the CLI without leaving your terminal:

```bash
bunx struere chat coach-assistant
```

This opens an interactive REPL with the agent. Send a message, see the response, inspect the tool calls. Useful for development. Useful as a self-improvement loop too: ask Claude Code to test all agents and look for failures. Claude runs the chat tool, captures the failures, and proposes fixes.

## A Sample WhatsApp Conversation

Send a WhatsApp message to the connected number:

> **You:** hi
>
> **Agent:** Hi Marco! We have a match on Sunday May 17 against the Wildcats. Are you available?
>
> **You:** yes I'm in
>
> **Agent:** Got it. You're confirmed for Sunday. I'll send you a reminder the day before.

The agent ran two tool calls. First, `get_player_by_phone(+61...)` looked up your number in the player table from the seed step. Second, `set_availability(matchId, playerId, "available")` updated the callup record.

Refresh the dashboard. The match's callup now shows you as available.

## The Cancellation Flow

Send another message:

> **You:** never mind, I can't make it
>
> **Agent:** Sorry to hear that. I'll find a replacement.

The WhatsApp agent updates your availability to `unavailable` and triggers `agent.chat` to the Voice Replacement agent with the match details and the list of candidate replacements (active players not on the callup).

A few seconds later, your phone rings. The voice agent.

> **Voice Agent:** Hi, this is the coach bot for Sportistics. We have a match on May 15 against the Wildcats and we need a sub. Can you play?
>
> **You:** Yeah I can play.
>
> **Voice Agent:** Awesome. I'll add you to the callup. Thanks!

The voice agent calls `set_availability` with your ID and `available`, and the callup updates again. Coach didn't lift a finger.

## The Coach Assistant

Open the in-app chat widget. Ask the coach assistant a question:

> **Coach:** who scored the most points this month?
>
> **Coach Assistant:** This month, your top scorers are: 1) Camila (23 points), 2) Tomás (18 points), 3) Sofía (15 points). Want a breakdown by match?

This agent has read access to events, players, matches, and computed stats. It has no write access. The role definition handles that. The agent answers questions about the team without touching state.

## The Weekly Digest

The cron trigger runs every Sunday at 8 PM. It calls the Weekly Digest agent, which queries the events of the past week, the matches that finished, the training sessions, and any flagged player notes. It composes an email and sends it through the email integration.

Trigger it manually from the CLI to test:

```bash
bunx struere triggers run weekly-digest
```

Open the coach's inbox. There's a digest with the week's results, the top scorers, the players who reported pain in training, and the next match.

# The Result

After ten minutes, you have:

- A working frontend with real data
- Four agents on four channels: in-app chat, WhatsApp, voice, email
- A shared database with five entity types
- One cron trigger and one cross-agent orchestration loop
- Zero lines of custom backend code

Every piece is editable as code. To change the system prompt for the WhatsApp agent, edit the file. To add a new entity field, edit `entity-types/player.ts` and re-sync. If a tool misbehaves, ask Claude Code to fix it.

# Key Takeaways

- **One database, four channels.** The hard part of multi-channel agent systems is keeping state consistent. Struere's shared entity layer solves it by default.
- **Claude Code does the backend work.** You write prompts. Claude reads the docs (via the skill) and writes the agent definitions, tool implementations, and triggers.
- **Studio vs local is a token-spending decision.** Studio is fastest for non-developers. Local saves your inference budget and gives you version control.
- **Agents call each other.** The WhatsApp to voice replacement loop is one `agent.chat` call. Multi-agent orchestration without orchestrator infrastructure.
- **Self-healing is a prompt.** "Test the agents and find what's broken" is the workflow.

# Next Steps

- Clone the [boilerplate repo](https://github.com/MarcoNaik/sportistics-example) and run through the build yourself
- Read the [WhatsApp integration guide](https://docs.struere.dev/integrations/whatsapp.md) and the [voice integration guide](https://docs.struere.dev/integrations/voice.md)
- Browse other build tutorials: [dental booking on WhatsApp](/blog/build-dental-booking-agent-whatsapp-ai), [fitness class booking](/blog/automate-fitness-class-booking-ai-whatsapp-agent)
- Join the [Discord](https://discord.gg/struere). I'm onboarding every new builder by hand right now.

If you want to ship a multi-channel agent system this weekend, [sign up for Struere](https://app.struere.dev) and use the discount code from the video for a free month of premium. Reply to the welcome email and I'll help you scope it.

## Frequently Asked Questions

### Do I need to write any backend code to follow this tutorial?

You start from a frontend boilerplate with mock data, then send prompts to Claude Code that scaffold the database, the agents, and the integrations. The whole backend is generated and synced to Struere from the terminal. You manage configuration files.

### Why use the local CLI instead of Struere Studio?

Studio works inside the dashboard with no setup. The local CLI uses your existing Claude Code or Cursor subscription instead of consuming Struere inference tokens. You also get version-controlled files, full editor support, and you can inspect every entity, agent, and trigger as code.

### Can the agents share the same data layer?

All four agents in the tutorial read and write to the same entities (Player, Match, Callup, Event). The WhatsApp agent updates a player's availability, the voice agent reads the same record to find a replacement, and the email agent queries the same data for the weekly digest. One database, four channels.

### How does the voice call replacement flow work?

When the WhatsApp agent detects that a player can't make a match, it triggers an outbound voice call through Twilio to a candidate replacement. The voice agent (powered by OpenAI Realtime) asks if they can play, parses the response, and updates the callup if they accept. The whole loop runs without human intervention.

### Can I customize the prompts to fit a different industry?

The pattern is general: a frontend boilerplate, entities for your domain, four agents on four channels, and triggers that orchestrate them. Swap volleyball for tutoring, dental booking, real estate, or any industry where users interact across multiple channels and the data lives in a shared database.
