You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copilot review on PR #121 caught eight factual inaccuracies in the
runbook (most because I wrote from inferred knowledge rather than
verified code paths). Each fix below is grounded in a re-read of
the relevant source file.
- Integration table (Line 19): the demo/live switch lives in the
integration *clients* (lib/integrations/google-maps.client.ts,
lib/integrations/smtp.client.ts), not in the services
(geocoding.service / route-optimizer.service / email.service).
Rewrote the row "where the demo path is implemented" to point at
the correct file. Also clarified that webhook signature
verification on /api/webhooks/whatsapp uses HMAC SHA256 against
WHATSAPP_APP_SECRET.
- Health-check baseline (Line 27 + Line 298 expected JSON): n8n
reports up/unreachable (not configured/unconfigured); the response
has six check groups (not the five I'd implied earlier). Documented
the exact shape per check group, the rules that produce the
top-level status (healthy / degraded / unhealthy), and the example
"expected" JSON now matches what /api/health actually returns.
- Mapbox reference (Line 81): repo doesn't use Mapbox for the map
fallback; it renders a static placeholder when
NEXT_PUBLIC_GOOGLE_MAPS_BROWSER_KEY is unset. Reworded.
- Geocode verification (Line 107): /api/route-planning/geocode
persists coordinates to the Yard record but returns only
{ success, message }. Updated the expected response and added a
follow-up GET /api/yards/:id step to confirm lat/long stored.
- Template wording (Line 175 + Line 180): reminders today use
whatsappService.sendTextMessage (free text from
reminder.service.ts), not sendTemplateMessage. Replaced the
misleading "all flows use templates" claim with a clear table:
appointment confirmations need template approval today; reminders
+ stock-replies will need it for outside-window sends. Template
body strings live in lib/services/{reminder,stock-reply}.service.ts
build* functions, NOT in messages/{en,fr}.json — pointed
operators at the actual source so they don't waste time grepping
the i18n catalogs.
- WhatsApp outbound smoke test (Line 202): the original example
POSTed to /api/demo/simulate-whatsapp which is an INBOUND webhook
simulator (and is blocked when DEMO_MODE=false), so it could
never exercise a real send. Replaced with two safe operator paths:
(a) trigger /api/reminders/check on a backdated due date, or
(b) book a real test appointment through the operator UI. Both
exercise sendTemplateMessage / sendTextMessage against the live
Meta endpoint when DEMO_MODE=false on Production.
- Stop-gate count (Line 327): said "five health-check items" but
there are six (database + environment + n8n + whatsapp + smtp +
googleMaps). Fixed the count and reworded the smoke-test list to
reference real operator UI paths instead of imagined endpoints.
No code changes; pure documentation correctness.
https://claude.ai/code/session_01JBMfnqc8QXZRqCG3Tm7hHx
| Integration | Demo / live switch | Live path | Webhook |
14
+
|---|---|---|---|
15
+
| Google Geocoding + Route Optimization |`lib/integrations/google-maps.client.ts` (routes to `lib/demo/maps-simulator.ts` in demo mode, calls real Geocoding / Route Optimization APIs in live mode) |`https://maps.googleapis.com/maps/api/geocode/json`, `https://routeoptimization.googleapis.com/v1/projects/{id}:optimizeTours`| n/a |
16
+
| WhatsApp Business (Cloud API) |`lib/services/whatsapp.service.ts` (routes to `lib/demo/whatsapp-simulator.ts` in demo mode) |`https://graph.facebook.com/v21.0/{phone_id}/messages`|`app/api/webhooks/whatsapp/route.ts` (HMAC SHA256 signature verified against `WHATSAPP_APP_SECRET`) |
17
+
| n8n | health-check pings `${N8N_HOST}` and reports `up`/`unreachable`; webhook handlers fail-closed when `N8N_API_KEY` is unset in production |`${N8N_PROTOCOL}://${N8N_HOST}:${N8N_PORT}`| bidirectional |
18
+
| SMTP / email |`lib/integrations/smtp.client.ts` (routes to `lib/demo/email-simulator.ts` in demo mode) | nodemailer over SMTPS | inbound n8n → `/api/webhooks/email`|
20
19
21
20
The mode switch is per-integration and driven by env vars. `DEMO_MODE=true` forces the simulator across the board; setting `EQUISMILE_LIVE_MAPS=true` flips just Maps to live while everything else stays simulated (useful for Phase 1 client demos that need real route maps but no real WhatsApp sends).
You should see `checks.database.status: "up"` and `checks.environment.missing: []`. Each integration shows `"unconfigured"` until you finish its section.
26
+
27
+
The response has six check groups with these shapes:
28
+
29
+
| Check | Status values |
30
+
|---|---|
31
+
|`database`|`up` / `down` (latency ms) |
32
+
|`environment`|`ok` / `missing` (with a `missing[]` array) |
33
+
|`n8n`|`up` / `unreachable` (with `url`) |
34
+
|`whatsapp`|`configured` / `unconfigured`|
35
+
|`smtp`|`configured` / `unconfigured`|
36
+
|`googleMaps`|`configured` / `unconfigured`|
37
+
38
+
Top-level `status` is `healthy` if everything is green, `degraded` if `n8n.status: "unreachable"` (or any optional check is down), `unhealthy` if `database.status: "down"` or `environment.status: "missing"`. Before this runbook starts, expect `database: up`, `environment: ok`, the three configured-flag checks at `unconfigured`, and `n8n` likely `unreachable` (so overall `degraded`).
28
39
29
40
---
30
41
@@ -74,7 +85,7 @@ In the Cloud Console for the project you just created:
74
85
75
86
### 2.4 (Optional) Browser-side key for inline maps
76
87
77
-
If you want the customer-detail map embed to use real tiles instead of the static Mapbox demo:
88
+
If you want the customer-detail map embed to render an interactive Google map instead of the current static placeholder fallback (rendered when `NEXT_PUBLIC_GOOGLE_MAPS_BROWSER_KEY` is unset):
78
89
- Repeat 2.3 to generate a second key.
79
90
- This time set **Application restrictions → HTTP referrers** to `*.vercel.app/*` and your custom domain.
80
91
- API restriction: tick **Maps JavaScript API** only.
# expect non-null lat/long values when the geocode succeeded
108
126
```
109
127
110
128
---
@@ -169,17 +187,25 @@ The "Temporary access token" expires after 24h. For production:
169
187
170
188
### 3.6 Message template approvals
171
189
172
-
Templates are pre-defined, brand-vetted message bodies — required for any send outside the 24-hour customer service window. The repo's reminder + confirmation flows all use templates.
190
+
Templates are pre-defined, brand-vetted message bodies — required by Meta for any **session-initiated** outbound send (i.e. outside the rolling 24-hour customer service window).
191
+
192
+
**Which flows currently need Meta-approved templates today vs. later:**
173
193
174
-
For each template key in `lib/demo/template-registry.ts` (9 templates: 2 appointment, 3 reminder, 4 FAQ), submit it to Meta:
| Appointment confirmations |`whatsappService.sendTemplateMessage('appointment_confirmation_v1', …)`|**Yes** — must be approved before live sends |
197
+
| Stock-reply / FAQ replies (`StockReplyModal`) |`sendTemplateMessage(<faq_*>)` within the 24h window after an inbound enquiry | Optional — works as free-text in the customer-care window, but template-approved versions unlock outside-window sends |
198
+
| Dental / vaccination / overdue-invoice reminders |`whatsappService.sendTextMessage(...)` (free text from `lib/services/reminder.service.ts`) |**Not yet today** — but reminders typically fire outside the 24h window, so getting templates approved unblocks reliable production sending |
199
+
200
+
For the templates that ARE referenced as registered keys in `lib/demo/template-registry.ts` (9 keys: 2 appointment, 3 reminder, 4 FAQ), submit each to Meta:
175
201
176
202
1. Meta WhatsApp panel → **Message Templates → Create Template**.
177
-
2. Category: `Utility` for all reminders + FAQs; `Marketing`*not* applicable to EquiSmile.
178
-
3. Name: must match the registry exactly — e.g. `appointment_reminder_v1`. Lower-case, snake_case, version suffix.
179
-
4. Languages: `English (en)` and `French (fr)` — pull the body strings from `messages/en.json` + `messages/fr.json` under the matching key. Replace operator-side variables (e.g. `{{customer_name}}`) with placeholders Meta accepts (`{{1}}`, `{{2}}` etc.) — preserve the order.
203
+
2. Category: `Utility` for all reminders + FAQs; `Marketing` not applicable to EquiSmile.
204
+
3. Name: must match the registry key exactly — e.g. `appointment_reminder_v1`. Lower-case, snake_case, version suffix.
205
+
4. Languages: `English (en)` and `French (fr)`. The canonical body strings are **assembled in code**, not in `messages/{en,fr}.json`. For each template, copy the body text from its corresponding `build…` function in `lib/services/reminder.service.ts` (e.g. `buildDentalDueReminder`, `buildVaccinationDueReminder`, `buildOverdueInvoiceReminder`) and `lib/services/stock-reply.service.ts` for the FAQ bodies. Replace function parameters (e.g. `customer_name`) with positional Meta placeholders (`{{1}}`, `{{2}}`) — preserve the order shown in the function's parameter list.
180
206
5. Submit. Approval is automated for utility messages and usually clears in <60 seconds; if rejected for "policy violation" it's almost always a missing footer or unclear `{{n}}` mapping.
181
207
182
-
> Once a template is approved, set `WHATSAPP_CONFIRMATION_TEMPLATE` and `WHATSAPP_REMINDER_TEMPLATE`env vars to the registry key (default values are already correct: `appointment_confirmation_v1`, `appointment_reminder_v1`).
208
+
> Once approved, the existing env-var defaults are already correct: `WHATSAPP_CONFIRMATION_TEMPLATE=appointment_confirmation_v1`, `WHATSAPP_REMINDER_TEMPLATE=appointment_reminder_v1`. Override these only if you've registered renamed template versions in Meta.
If `n8n.status: "unreachable"` (e.g. you've intentionally not deployed n8n yet), top-level `status` will be `"degraded"` while the rest is configured — that's an acceptable production state if your operator workflows don't yet depend on n8n.
-All five health-check items report `configured` or `up`.
322
-
-Send one real WhatsApp template message to a test number — receipt confirmed on phone.
323
-
-Run a real geocode for a Swiss yard — coordinates persisted to DB.
324
-
-Trigger one real route optimisationagainst three live yards — proposal saved.
325
-
- Confirm the inbound WhatsApp webhook receives a real test message and creates an Enquiry.
366
+
-`/api/health` shows `database: up`, `environment: ok`, `whatsapp / smtp / googleMaps: configured`, and `n8n: up` (or `unreachable` if you're deferring n8n) — six checks total.
367
+
-Confirm one real WhatsApp template send: book a real test appointment from `/en/route-runs/[id]` → Approve → Book; the booking flow fires `appointment_confirmation_v1` and the test phone receives the message.
368
+
-Confirm one real geocode: from `/en/yards/[id]` admin action, geocode a Swiss yard; refresh and confirm `latitude` + `longitude` are populated on the yard record.
369
+
-Confirm one real route optimisation: from `/en/planning`, generate routes against three real geocoded yards; route-run is saved with the optimised stop order.
370
+
- Confirm the inbound WhatsApp webhook: from a Meta-allowlisted phone, send a real WhatsApp message to your business number; observe a new Enquiry appear in `/en/enquiries`.
326
371
327
372
If all five smoke tests pass, integrations are production-ready. Mark UAT-INT-01..04 + UAT-PLN-02 verdicts as PASS in `docs/UAT_v2_VALIDATION.md` next pass.
0 commit comments