Skip to content

feat(whatsapp): send routes through configured backend - #226

Open
Gill87 wants to merge 9 commits into
benevolentbandwidth:mainfrom
Gill87:feat/whatsapp-backend-sender
Open

feat(whatsapp): send routes through configured backend#226
Gill87 wants to merge 9 commits into
benevolentbandwidth:mainfrom
Gill87:feat/whatsapp-backend-sender

Conversation

@Gill87

@Gill87 Gill87 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the UI-only WhatsApp mock with protected backend route delivery.
  • Send drivers a readable stop-list message instead of raw route JSON.
  • Harden sends with failure logging, transient retries, timeouts, and capped concurrency.
  • Keep the WhatsApp send secret on the Next.js runtime only.
  • Rename the export option to Export Routes and simplify related copy.

Motivation

  • Dispatch needs route sends to reach the production WhatsApp integration while keeping the shared send secret on the server.
  • Drivers need a clear stop list (name, address, phone, stop index), and operators need reliable diagnostics when a send fails.

Changes

  • Frontend: Send one to/message request per route to the configured backend, attach the send-secret header, and map returned WhatsApp message IDs to per-route results.
  • Frontend: Format each WhatsApp body with a driver greeting and per-stop name/address/phone/index blocks.
  • Frontend: Log non-OK and network failures, treat HTTP 2xx without a message ID as sent, retry 5xx/429 with backoff, enforce a request timeout, and limit concurrent sends to 5.
  • Frontend: Rename Export JSON to Export Routes, simplify the method description, and update the export modal subtitle.
  • Infrastructure: Configure WHATSAPP_SEND_ROUTE_SECRET as a runtime App Hosting secret.
  • Tests: Cover message formatting, successful and failed backend responses, 2xx-without-id handling, retry behavior, concurrency capping, export copy, and the export-method browser flow.

Validation

Frontend

  • npm --prefix app/ui run typecheck
  • npm --prefix app/ui run format:check
  • npm --prefix app/ui run test -- src/tests/whatsappClient.test.ts src/tests/formatRouteMessage.test.ts — 12 tests passed
  • npm --prefix app/ui run test -- src/tests/exportMethodModal.test.ts — 2 tests passed
  • npm --prefix app/ui run lint - clean
  • npm --prefix app/ui run test - passed
  • npm --prefix app/ui run test:e2e — Playwright Chromium unavailable locally
  • npm --prefix app/ui run build — not run in this update
  • npm --prefix app/mobile run lint — mobile package unchanged
  • npm --prefix app/mobile run typecheck — mobile package unchanged

Backend

  • Backend package checks — backend package unchanged

Risk

  • Deployment requires App Hosting and the backend to share the WHATSAPP_SEND_ROUTE_SECRET value. Missing runtime configuration prevents sends rather than exposing the secret to the client.
  • Free-form WhatsApp text still depends on an open customer-service window; cold sends outside that window can fail until an approved Meta template path exists.
  • HTTP 2xx responses without a WhatsApp message ID are treated as sent to avoid duplicate retries; support should use the warning logs if that anomaly appears.

Rollout and Recovery

  • Provision the runtime secret, deploy the UI, and verify a route send against the backend with a known driver number.
  • Confirm the delivered WhatsApp text matches the driver stop-list template.
  • Revert this PR to return to the mock sender if backend delivery must be disabled.

High-Signal PR Checklist

  • The summary states the user-visible and operational outcome.
  • The motivation explains why this change is needed now.
  • The change list separates frontend and infrastructure work.
  • Validation includes exact commands and relevant output.
  • Risks and rollback steps are called out.
  • Screenshots or request/response examples are omitted because this update is a server-side send-path and copy change.

Gill87 and others added 5 commits July 3, 2026 10:12
Replace the UI mock with protected backend delivery so route sends use production WhatsApp results.

Co-authored-by: Cursor <cursoragent@cursor.com>
Give dispatchers clear delivery results with failed driver names and actionable error details.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep export copy and dispatcher-facing send outcomes verified through the browser flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Gill87 Gill87 changed the title feat(whatsapp): send routes through configured backend feat(whatsapp): send routes with delivery outcomes Jul 18, 2026
Restore the prior send flow while retaining the Export Routes copy update.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Gill87 Gill87 changed the title feat(whatsapp): send routes with delivery outcomes feat(whatsapp): send routes through configured backend Jul 18, 2026
@Gill87

Gill87 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@markboenigk This PR removes the mock data and replaces it with a real call to WhatsApp along with a quick wording change in the UI that Kirill mentioned on Friday. I thought about adding more UI regarding a success/failure popup based on if WhatsApp sent the routes successfully or not to the drivers, but I decided to leave that for a future PR.

@Gill87

Gill87 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@markboenigk I do want to bring up a possible issue flagged by my agent regarding WhatsApp's policy:

WhatsApp's 24-hour session / template rule. The backend sends a free-form type: "text" message. WhatsApp Cloud API only allows free-form text inside an open 24-hour customer-service window; outside it, you must use a pre-approved message template. So unsolicited route sends to drivers who haven't recently messaged the business number will be rejected by Graph API and surface as status: "failed". This is a backend/platform constraint, not something this PR introduces, but it will block "cold" sends in production.

This could be a problem for production, I believe we need to get a template approved by WhatsApp in order to be able to cold send these messages to drivers.

@markboenigk

Copy link
Copy Markdown
Collaborator

Review: send routes through configured backend

Good change overall — the secret handling checks out (server-only, RUNTIME-only in apphosting.yaml, never reachable from the client bundle), the header/body contract matches the backend endpoint from #215, and the per-route error isolation via Promise.all + per-item try/catch is the right shape. A few things worth addressing before/after merge:

Error info is discarded on non-OK responses (medium)
sendRoute() never reads the response body or logs anything on a non-2xx status — a 429/500 with a useful backend error message just collapses into {status: "failed"} with no trace. Every other server-side client in this codebase (deliveryOptimizerClient.ts, session/exportSession.ts) logs or attaches status/body to a typed error; this will hurt support/debugging triage when a send fails in production.

Ambiguous "failed" on malformed-but-2xx response (medium)
If the backend returns 2xx with a body that doesn't parse into a message ID, the route is marked "failed" even though WhatsApp may have actually delivered it. If a "retry failed sends" UI gets built later, this could cause duplicate driver messages. Worth tracking as a known limitation now.

No retry/backoff or timeout (low)
Unlike deliveryOptimizerClient.ts, which already has a retry() utility (lib/utils/retry.ts) and an AbortController timeout, sendRoute has neither — a single transient 502/429 permanently fails that route.

Unbounded concurrent fan-out (low)
Promise.all(items.map(sendRoute)) sends all requests simultaneously with no concurrency cap. Combined with the lack of retry above, a burst that trips backend rate limiting turns into silent, permanent per-route failures for a whole batch.

Worth confirming intentional
The WhatsApp message body is JSON.stringify(item.route) — a raw JSON blob, not human-readable route text. If that's a placeholder pending a formatting pass, fine; otherwise drivers will receive unreadable messages.

Nice to have: ExportRoutesModal.tsx's subtitle still says "export as JSON files," now slightly out of step with the new "Export Routes" copy — pre-existing, out of scope for this diff, just flagging for a follow-up.

Test coverage is solid (success, backend-500/network/malformed-response failures, missing-env-var rejection, empty-batch) — matches what's described in the PR body. I also reran the checks marked "not rerun after final reversion" (format:check, full test, typecheck, build) against the PR head and all pass cleanly, with no leftover debris from the revert.

Nothing here blocks on security grounds — the secret is well-isolated. The error-swallowing and missing-retry points are the ones I'd want addressed before this carries real driver traffic at volume.

Gill87 and others added 2 commits July 25, 2026 14:27
Address PR review by logging failures, retrying transient errors, capping concurrency, treating 2xx without message IDs as sent, and sending a readable stop list instead of raw JSON.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@Gill87

Gill87 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Hey @markboenigk,

I went ahead and addressed the error handling concerns and formatted the message to match the template I sent over on Slack, although, I think we should discuss this more before sending the template for approval to Meta.

Avoid duplicate driver messages when a 5xx follows an upstream-accepted WhatsApp POST.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants