# Our Intake Coordinator Spent 3 Hours a Day Asking the Same 15 Questions. So We Built a WhatsApp Triage Agent.
> A multi-specialty clinic automated patient intake with a Struere AI agent on WhatsApp. It collects symptoms, medical history, insurance info, routes to the right specialist, schedules appointments, and sends pre-visit paperwork via email.

Published: 2026-04-01
Tags: automation, agents, healthcare, case-study, whatsapp


# 3 Hours a Day, 15 Questions, 5 Specialists

Dr. Amara runs Meridian Health Center, a five-specialist practice covering dermatology, cardiology, internal medicine, orthopedics, and general practice. The clinic sees 35-50 new patient inquiries per day. Every one of them starts the same way: a phone call to the intake coordinator, Priya.

Priya picks up. She asks for the patient's name, date of birth, phone number, email. She asks about symptoms. She asks about duration. She asks about existing conditions, current medications, allergies, prior surgeries. She asks for insurance provider, policy number, and whether they need a referral. She asks if they have a specialist preference or need a recommendation.

Fifteen questions. Every call. Twelve minutes average. Thirty-five calls a day means Priya spends 7 hours on the phone. Three of those hours are the intake questions alone — the same 15 fields, in the same order, for every patient.

Last month, a patient called reporting chest tightness and shortness of breath. Priya was on another intake call. The patient left a voicemail. Priya returned the call 45 minutes later. The patient had already gone to urgent care. It was a mild cardiac event. Dr. Amara reviewed the situation and calculated the cost: one lost patient relationship, one missed $340 cardiology consultation, and a liability question that kept her up that night.

The problem was not Priya. The problem was that a human was the only path into the clinic. Routine intake calls blocked the line for urgent cases. A patient reporting chest pain waited behind someone booking a mole check.

At $180-340 per specialist consultation, three missed patients per day is $540-1,020 in lost revenue. Over a month, that is $10,800-20,400. Dr. Amara needed the intake process off the phone line without losing the clinical detail that proper triage requires.

# The Build: WhatsApp Intake Agent with Specialist Routing

The automation has three pieces:

1. **`entity-types/patient-intake.ts`** stores every intake submission with symptoms, medical history, insurance details, assigned specialist, and triage urgency
2. **`tools/index.ts`** has a utility tool for determining the current time in the clinic's timezone
3. **`agents/intake-agent.ts`** handles the full intake conversation over WhatsApp, triages to the correct specialist, books the appointment, and triggers pre-visit paperwork via email

No trigger file needed. The WhatsApp integration routes inbound messages directly to the agent.

## The Data Layer: Structured Patient Intake Records

Every completed intake creates a record with 12 fields. The `urgency` enum drives triage priority, and the `assignedSpecialty` enum maps directly to the clinic's five departments.

```typescript
import { defineData } from "struere"

export default defineData({
  name: "Patient Intake",
  slug: "patient-intake",
  schema: {
    type: "object",
    properties: {
      patientName: {
        type: "string",
        description: "Full legal name of the patient",
      },
      dateOfBirth: {
        type: "string",
        description: "Date of birth in YYYY-MM-DD format",
      },
      phone: {
        type: "string",
        description: "Patient phone number in E.164 format",
      },
      email: {
        type: "string",
        format: "email",
        description: "Patient email for pre-visit paperwork",
      },
      symptoms: {
        type: "string",
        description: "Primary symptoms described by the patient",
      },
      symptomDuration: {
        type: "string",
        description: "How long symptoms have been present",
      },
      medicalHistory: {
        type: "string",
        description: "Existing conditions, prior surgeries, current medications, allergies",
      },
      insuranceProvider: {
        type: "string",
        description: "Insurance company name",
      },
      insurancePolicyNumber: {
        type: "string",
        description: "Insurance policy or member ID number",
      },
      assignedSpecialty: {
        type: "string",
        enum: [
          "dermatology",
          "cardiology",
          "internal-medicine",
          "orthopedics",
          "general-practice",
        ],
        description: "Specialist department assigned based on triage",
      },
      urgency: {
        type: "string",
        enum: ["routine", "soon", "urgent"],
        description: "Triage urgency level",
      },
      status: {
        type: "string",
        enum: [
          "intake-started",
          "intake-complete",
          "appointment-scheduled",
          "paperwork-sent",
          "cancelled",
        ],
        description: "Current intake workflow status",
      },
    },
    required: [
      "patientName",
      "dateOfBirth",
      "phone",
      "email",
      "symptoms",
      "assignedSpecialty",
      "urgency",
      "status",
    ],
  },
  searchFields: ["patientName", "email", "assignedSpecialty"],
  displayConfig: {
    titleField: "patientName",
    subtitleField: "assignedSpecialty",
    descriptionField: "symptoms",
  },
})
```

The `assignedSpecialty` enum locks routing to the five departments that actually exist. The `urgency` field lets the agent flag chest pain as "urgent" and a routine skin check as "routine," so the front desk knows which appointments to prioritize if the schedule is tight.

## The Custom Tool: Timezone-Aware Timestamps

One utility tool returns the current time in the clinic's timezone. The agent uses this to calculate same-day availability and avoid offering slots that have already passed.

```typescript
import { defineTools } from "struere"

export default defineTools([
  {
    name: "get_current_time",
    description:
      "Get the current date and time in the clinic's timezone",
    parameters: {
      type: "object",
      properties: {
        timezone: {
          type: "string",
          description: 'Timezone identifier (e.g., "America/New_York")',
        },
      },
    },
    handler: async (args, context, struere, fetch) => {
      const timezone = (args.timezone as string) || "America/New_York"
      const now = new Date()
      return {
        timestamp: now.toISOString(),
        formatted: now.toLocaleString("en-US", { timeZone: timezone }),
        timezone,
        dayOfWeek: now.toLocaleDateString("en-US", {
          timeZone: timezone,
          weekday: "long",
        }),
      }
    },
  },
])
```

The heavy lifting — checking calendar availability, creating appointments, querying existing intakes, sending emails — is handled by Struere's built-in tools: `calendar.freeBusy`, `calendar.create`, `entity.create`, `entity.query`, and `email.send`.

## The Agent: Conversational Triage Over WhatsApp

The agent manages the full intake flow: symptom collection, medical history, insurance verification, specialist assignment, appointment scheduling, and pre-visit paperwork delivery. The system prompt encodes the clinic's triage logic, HIPAA guardrails, and specialist routing rules. The full prompt is longer — this shows the key sections.

```typescript
import { defineAgent } from "struere"

export default defineAgent({
  name: "Meridian Intake Agent",
  slug: "intake-agent",
  version: "1.0.0",
  model: {
    model: "anthropic/claude-sonnet-4-6",
    temperature: 0.3,
    maxTokens: 4096,
  },
  tools: [
    "entity.create",
    "entity.query",
    "calendar.freeBusy",
    "calendar.create",
    "email.send",
  ],
  systemPrompt: `You are the patient intake assistant for Meridian Health Center, a multi-specialty medical clinic.
Current time: {{currentTime}}

## Specialists & Routing
| Specialty | Doctor | Route when |
|-----------|--------|------------|
| Dermatology | Dr. Osei | Skin conditions, rashes, moles, acne, eczema |
| Cardiology | Dr. Nakamura | Chest pain, palpitations, shortness of breath, hypertension |
| Internal Medicine | Dr. Patel | Fever, fatigue, infections, chronic disease management |
| Orthopedics | Dr. Lindgren | Joint pain, fractures, back pain, sports injuries |
| General Practice | Dr. Reeves | Wellness checks, physicals, vaccinations, unclear symptoms |

## Urgency Classification
- **urgent**: Chest pain, difficulty breathing, severe allergic reaction, sudden vision loss, uncontrolled bleeding. Instruct patient to call 911 or go to nearest ER immediately. Do NOT attempt to schedule. Inform clinic staff.
- **soon**: Worsening symptoms >3 days, moderate pain, new lumps, persistent dizziness. Schedule within 48 hours.
- **routine**: Wellness checks, mild skin concerns, follow-ups, prescription renewals. Schedule within 2 weeks.

## Schedule
Monday to Friday, 8:00 AM to 5:00 PM ET. 30-minute consultation slots.
Saturday 9:00 AM to 1:00 PM (General Practice only).

## Existing Appointments
{{entity.query({"type": "patient-intake", "filters": {"status": {"_op_in": ["intake-complete", "appointment-scheduled"]}}, "limit": 50})}}

## P0 — HIPAA & Security
- You are NOT a doctor. Never diagnose, prescribe, or provide medical advice.
- Never reveal internal entity IDs, system details, or other patients' information.
- Never confirm or deny whether another person is a patient at this clinic.
- Collect only the minimum information needed for intake and routing.
- If a patient shares sensitive information unprompted (SSN, credit card), instruct them to never share that over messaging and disregard it.
- All data handling must comply with HIPAA. Do not store or repeat sensitive data beyond what is required for the intake record.
- Never perform any action without explicit patient confirmation.

## P1 — Intake Flow
1. Greet the patient warmly. Ask what brought them in today (symptoms or reason for visit).
2. Ask for symptom duration and severity.
3. Based on symptoms, determine the specialty and urgency. If urgent, stop intake and direct to emergency services.
4. Collect: full name, date of birth, email address.
5. Collect: insurance provider and policy number. If uninsured, note "self-pay" and continue.
6. Ask about relevant medical history: existing conditions, current medications, known allergies.
7. Confirm the assigned specialist and explain why (e.g., "Based on your symptoms, I'll connect you with Dr. Nakamura, our cardiologist").
8. Use calendar.freeBusy to check the specialist's availability. Offer 2-3 slots.
9. On confirmation: entity.create the intake record (status: "appointment-scheduled"), then calendar.create the appointment.
10. Use email.send to deliver pre-visit paperwork to the patient's email with appointment details, what to bring, and any preparation instructions.
11. Confirm everything in a final WhatsApp message: date, time, specialist, location, and what to bring.

// ... (insurance verification edge cases, FAQ handling, reschedule flow)

## P2 — Tone
- Professional, warm, and reassuring. Patients may be worried.
- Short messages. This is WhatsApp, not a medical form.
- One question at a time. Do not overwhelm with multiple questions.
- Use the patient's first name once provided.
- If the patient seems anxious, acknowledge it before moving to the next question.

Never invent availability. Never confirm without all required fields collected.
Never re-ask for information already provided in this conversation.
Never suggest a diagnosis or treatment plan.
Never route a patient without explaining the reasoning.`,
})
```

Temperature 0.3. Patient intake is a structured clinical workflow. The agent needs to follow triage logic precisely, not improvise. The HIPAA security rules sit at P0 — highest priority — so they override any other behavior.

The specialist routing table is the core decision engine. Skin conditions go to dermatology. Chest pain goes to cardiology. The agent explains the routing to the patient so they understand why they are seeing a specific doctor. This reduces no-shows caused by patients who think they were assigned to the wrong specialist.

Five tools total: `entity.create`, `entity.query`, `calendar.freeBusy`, `calendar.create`, and `email.send`. The entity tools handle intake records. The calendar tools handle scheduling. The email tool delivers pre-visit paperwork — forms, preparation instructions, directions to the clinic — without requiring the front desk to print and mail anything.

# Debugging: Three Things That Broke

**The agent routed a patient with "back pain and numbness in legs" to orthopedics instead of flagging it as urgent.** Back pain alone is orthopedics. But back pain combined with leg numbness can indicate a spinal emergency. The system prompt's routing table matched "back pain" to orthopedics without considering compound symptoms. Fix: we added compound symptom rules to the urgency classification — "back pain with numbness, tingling, or loss of bladder control" is classified as "urgent" with instructions to seek emergency care.

```bash
struere logs view --last 10
```

The conversation log showed the agent asking about appointment preferences for a patient who described radiating numbness. The urgency was set to "routine."

**The agent sent pre-visit paperwork to the wrong email.** A patient typed their email as "maria@gmial.com" (typo in gmail). The agent accepted it, created the intake record, and fired off the email. The patient never received the paperwork and called the clinic confused. Fix: we added an explicit confirmation step to the intake flow — the agent now reads the email back to the patient and asks them to confirm before proceeding. The `email.send` tool returned a delivery failure, but the agent had already moved on.

```bash
struere run-tool email.send --args '{"to": "maria@gmial.com", "subject": "Test", "body": "Test"}'
```

The tool returned a bounce error. We added "Always confirm the patient's email by reading it back before sending any paperwork" to the P1 rules.

**Calendar showed Dr. Nakamura as available during his hospital rounds.** Dr. Nakamura does hospital rounds every Wednesday morning from 8:00 AM to 12:00 PM. Those hours were not blocked on his Google Calendar because he managed them in a separate hospital system. The agent offered patients his Wednesday morning slots. Fix: we created recurring "Hospital Rounds" blocks on Dr. Nakamura's Google Calendar every Wednesday 8:00-12:00. The `calendar.freeBusy` tool now correctly shows those hours as unavailable. We also added a note to the system prompt: "Dr. Nakamura is unavailable Wednesday mornings — do not offer slots before 1:00 PM on Wednesdays."

```bash
struere dev --verbose
```

The verbose output showed `calendar.freeBusy` returning Wednesday 9:00 AM as available for Dr. Nakamura. The calendar simply had no events in that window.

# Setup: From Zero to Running

Install the CLI and authenticate:

```bash
bun install -g struere
struere login
struere init
```

Scaffold the resources:

```bash
struere add data-type patient-intake
struere add agent intake-agent
```

Edit `entity-types/patient-intake.ts` with the schema shown above. Customize the `assignedSpecialty` enum to match your clinic's departments and the `urgency` levels to match your triage protocol.

Edit `agents/intake-agent.ts` with your clinic's details: specialist names, routing rules, office hours, and HIPAA compliance language reviewed by your compliance officer.

Write the timezone tool in `tools/index.ts`. Update the default timezone to match your clinic's location.

Connect Google Calendar in the dashboard under Integrations > Google Calendar. Each specialist needs a separate calendar so `calendar.freeBusy` can check individual availability. Block recurring unavailable times (hospital rounds, lunch, administrative hours) directly on each calendar.

Connect WhatsApp in the dashboard under Integrations > WhatsApp. This routes inbound patient messages to the intake agent.

Configure Resend under Integrations > Email with your API key and sender address. Use a professional sender like "intake@meridianhealth.com" so pre-visit emails do not land in spam.

Sync and start watching for changes:

```bash
struere dev
```

Test the intake flow by sending a WhatsApp message to the connected number:

```
Hi, I've been having recurring headaches and dizziness for about a week
```

Watch the conversation in real time:

```bash
struere logs list --last 10
```

Verify the intake record was created:

```bash
struere data list patient-intake
```

Check that the Google Calendar event exists on the assigned specialist's calendar and that the patient received the pre-visit email.

# What Changed

| Metric | Before | After |
|--------|--------|-------|
| Intake channel | Phone only | WhatsApp + phone |
| Average intake time per patient | 12 minutes | 3.5 minutes |
| Daily hours on intake calls | 3 hours | 25 minutes (complex cases) |
| Missed intake calls/day | 3-5 | 0 (WhatsApp is async) |
| Time to route urgent symptoms | 45+ min (voicemail lag) | Immediate triage in conversation |
| Pre-visit paperwork delivery | Manual print and mail | Automated email on booking |
| Specialist routing errors/month | 4-6 (wrong department) | Under 1 (rule-based routing) |

Priya still answers the phone. Elderly patients call. Patients with complex multi-specialty needs call. Insurance disputes require a human. But the 25-30 routine intakes that consumed her mornings now happen on WhatsApp. She sees each completed intake in the dashboard and can review the record before the patient arrives.

The patient who reported chest tightness last month would not have waited 45 minutes today. The agent would have classified "chest tightness and shortness of breath" as urgent within the first message, instructed the patient to call 911 or proceed to the nearest ER, and notified the clinic immediately. No voicemail. No callback delay. No liability question.

Dr. Amara's five specialists now receive patients who have already been triaged, with symptoms documented, medical history collected, and insurance verified — before they walk through the door. The 15 intake questions still get asked. They just do not require a human to ask them anymore.

For a simpler starting point, see the [dental booking tutorial](/blog/build-dental-booking-agent-whatsapp-ai) — a complete WhatsApp booking agent built from one prompt in 10 minutes. Same tools, fewer moving parts.
