WhatsApp Patient Phone Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Add a per-patient whatsapp_phone field used exclusively for outbound WhatsApp messaging, gated behind the whatsapp_api addon module.
Architecture: Single nullable whatsapp_phone column on the patients table, encrypted via the existing AES-256-GCM PHI pipeline (encryptPatientPHI/decryptPatientPHI). Frontend shows a “Also on WhatsApp” toggle next to the phone field — only visible when the clinic has the whatsapp_api addon. All WA sends use whatsappPhone exclusively, no fallback to phone.
Tech Stack: Drizzle ORM (schema push), react-international-phone (flag picker for both phone fields, all countries), Zod (validation schema), Hono routes, React state (toggle logic).
Task 1: Install react-international-phone
Files:-
Modify:
ui/package.json - Step 1: Install
added 1 package (or similar).
- Step 2: Verify
- Step 3: Commit
Task 2: Create PhoneInput wrapper component
Files:-
Create:
ui/src/components/ui/PhoneInput.tsx - Step 1: Create the file
- Step 2: Commit
Task 3: Add whatsappPhone column to patients schema
Files:-
Modify:
server/src/schema/patients.ts - Step 1: Add the column
server/src/schema/patients.ts, after line 15 (phone: text('phone').notNull()), insert:
phone on line 15 and whatsappPhone on the next line.
- Step 2: Verify TypeScript
- Step 3: Push schema to DB
whatsapp_phone column (y). This runs drizzle-kit push:pg and applies the ALTER TABLE to the Neon PostgreSQL database.
- Step 4: Commit
Task 4: Add whatsappPhone to PHI encryption
Files:-
Modify:
server/src/lib/encryption.ts(line 177) - Step 1: Add field to PATIENT_PHI_FIELDS
server/src/lib/encryption.ts, change line 177 from:
encryptPatientPHI (line 183) and decryptPatientPHI (line 199) iterate PATIENT_PHI_FIELDS. The null-guard at line 186 (encrypted[field] != null && typeof encrypted[field] === 'string') means null whatsappPhone values are skipped automatically — no extra handling needed.
- Step 2: Verify TypeScript
- Step 3: Commit
Task 5: Add whatsappPhone to Zod validation schema and patient routes
Files:-
Modify:
server/src/lib/validation.ts(around line 44) -
Modify:
server/src/routes/patients.ts(lines 290, 303–316, 535–538) - Step 1: Update Zod schema
server/src/lib/validation.ts, in patientBaseSchema (around line 44), add whatsappPhone after insurancePolicyNumber:
patientUpdateSchema = patientBaseSchema.partial() (line 49), both create and edit inherit this field automatically.
- Step 2: Update CREATE handler
server/src/routes/patients.ts, around line 290, after:
encryptPatientPHI({...}) call (lines 303–316), add whatsappPhone after phone:
- Step 3: Update EDIT handler
/:id handler (around line 535), change:
encryptPatientPHI(normalizedBody) (line 540) already receives and encrypts the normalized body — no further changes needed in the update path.
- Step 4: Verify TypeScript
- Step 5: Commit
Task 6: Update PatientForm.tsx — phone picker + WhatsApp toggle
Files:-
Modify:
ui/src/components/patients/PatientForm.tsx - Step 1: Add imports
PatientForm.tsx, add after the existing import block:
- Step 2: Add module check and WhatsApp state
PatientForm component, after the existing useState calls (around line 48), add:
- Step 3: Update the useEffect reset
useEffect (around line 53), inside the if (initialData) branch, after setAccountCheckDone(true), add:
else branch (new patient reset), after setAccountExists(false), add:
- Step 4: Update handleSubmit to include whatsappPhone
handleSubmit, find the onSubmit(formData) call (around line 151). Replace it with:
- Step 5: Replace the phone Input with PhoneInput + toggle
- Step 6: Build to verify
- Step 7: Commit
Task 7: Update PatientDetails.tsx — WhatsApp row
Files:-
Modify:
ui/src/components/patients/PatientDetails.tsx(around line 393) - Step 1: Confirm useModules is already imported
const { hasModule } = useModules(); — no import changes needed.
- Step 2: Add WhatsApp row after the phone block
</div> of that block, add:
- Step 3: Build to verify
- Step 4: Commit
Task 8: Fix appointment-reminders — use decrypted whatsappPhone
Files:- Modify:
server/src/scheduled/appointment-reminders.ts(lines 1–10, 64, 166–202)
patient.phone usage at line 166 sends the encrypted string to the WhatsApp API, which silently fails. This task fixes that by decrypting and switching to whatsappPhone.
- Step 1: Add decryptPatientPHI import
appointment-reminders.ts, check if decryptPatientPHI is already imported. If not, add it to the existing encryption import or add a new import:
- Step 2: Decrypt patient in the loop
for (const item of upcomingAppointments) loop, after line 64 (const { appointment, patient, doctor, clinic } = item;), add:
- Step 3: Update WhatsApp send block
- Step 4: Verify TypeScript
- Step 5: Commit
Task 9: Update whatsapp-webhook.ts — match by whatsappPhone first
Files:-
Modify:
server/src/routes/whatsapp-webhook.ts(lines 294–308) - Step 1: Add whatsappPhone to the patient select
handleIncomingMessage, change the db.select({...}) call (lines 294–303) from:
- Step 2: Update matching logic
- Step 3: Verify TypeScript
- Step 4: Commit
Task 10: Deploy and smoke test
Files:- Deploy server and UI
- Step 1: Deploy server
Published with no errors.
- Step 2: Build and deploy UI
- Step 3: Force-promote canonical deployment
- Step 4: Smoke test
- Addon-gated (clinic WITHOUT whatsapp_api): Open patient form → phone field shows flag picker, no “Also on WhatsApp” toggle visible anywhere.
- Addon-gated (clinic WITH whatsapp_api): Open patient form → phone field shows flag picker, green “Also on WhatsApp” toggle visible next to it.
- Toggle ON (default): Create new patient → toggle is ON → save → open patient detail → WhatsApp row shows same number as phone.
- Toggle OFF, different number: Edit patient → toggle OFF → WhatsApp field appears → enter different number → save → open detail → WhatsApp row shows the different number.
- Toggle OFF, blank: Edit patient → toggle OFF → leave WhatsApp field empty → save → open detail → WhatsApp row hidden.
-
Appointment reminder fires: Trigger a reminder manually or wait for the scheduled window. Confirm WA goes to
whatsappPhoneand notphone.

