Verify the multimodal "send link to customer" SMS end-to-end¶
Last reviewed: 2026-08-28
Use this after deploying a multimodal-SMS fix (or when a customer reports "no
link arrived") to prove the whole path works — not just that the
conversation-api validator stopped rejecting the request. The path has several
stages and, until 2026-08-28, a 400 at the first stage
(_allowed_surface_base_url)
masked every stage behind it, so a green validator is necessary but not
sufficient. See env-vars.md → SURFACE_APP_BASE_URL
and the conversation-api invariant.
The path (where each stage can fail)¶
ai-orchestrator a2ui_tool._send_multimodal_sms
│ POST /conversations/{id}/multimodal-sms { surface_app_base_url, phone, … }
▼
conversation-api multimodal_sms_endpoint
│ 1. _allowed_surface_base_url ← the 400 "must be HTTPS" bug lived here
│ 2. resolve share link, consent check
│ POST /share-codes (async-agent) ← agents:manage bearer required
▼
async-agent share-code send → SMS provider (SNS / Twilio / …)
▼
customer's phone
0. Precondition — confirm the fix is actually deployed¶
The config change (SURFACE_APP_BASE_URL → external HTTPS) fixes it on the
current image via an ArgoCD sync alone; the code change needs a new image.
Confirm the running conversation-api env before testing:
# read-only; the pod name will differ
kubectl -n trueserve exec deploy/conversation-api -- \
printenv SURFACE_APP_BASE_URL SURFACE_APP_URL
Expect both to be the external https://surface-app.<env>.trueserve.deloitte.com.
If SURFACE_APP_BASE_URL is still http://surface-app:3001, ArgoCD has not
synced the values change yet — stop; the test will fail at stage 1.
Optionally prove the live validator no longer throws (read-only, no request sent):
kubectl -n trueserve exec deploy/conversation-api -- python3 - <<'PY'
import http_api as h
u = h.SURFACE_APP_URL or h.SURFACE_APP_BASE_URL
print("validator ->", h._allowed_surface_base_url(u)) # prints the URL, no exception
PY
1. Trigger a multimodal SMS¶
Place a multimodal test call/session (Preview in Agent Studio, or a real inbound
call) with an agent that has A2UI multimodal enabled, then ask it to send the
visual link to your phone. Use a real, reachable handset number, not the
emulator default +15550100000 — the provider silently drops obviously-fake
numbers and that reads as "no SMS arrived" for a reason unrelated to this fix.
2. Confirm success in the logs (in order)¶
Tail both services while you trigger:
kubectl -n trueserve logs deploy/ai-orchestrator -f | grep -i 'A2UITool\|multimodal'
kubectl -n trueserve logs deploy/async-agent -f | grep -i 'share-code\|sms\|deliver'
Success looks like this, top to bottom:
| Stage | Service | Log line (substring) |
|---|---|---|
| Request sent | ai-orchestrator | [A2UITool] Sending multimodal SMS request to …/multimodal-sms |
| Validator passed | ai-orchestrator | no status=400 line follows — instead a 2xx / SMS send …ok |
| Share code issued | async-agent | POST /share-codes → 200 |
| Delivered | async-agent | send result with delivered: 1 (not 0) |
| Phone | — | the link actually arrives |
The single most important signal is the absence of
[A2UITool] Multimodal SMS request failed: status=400, body={"detail":"surface_app_base_url must be HTTPS"}.
That exact line is the bug this runbook exists for.
3. What a downstream failure looks like (the bug behind the bug)¶
Now that stage 1 passes, a failure will surface further down — these were
invisible before because the 400 aborted first:
| Symptom | Stage | Likely cause |
|---|---|---|
ai-orchestrator logs status=403, body …not allowed |
1 | The URL the orchestrator sends is HTTPS but not in the allow-list — SURFACE_APP_URL/SURFACE_APP_BASE_URL disagree between orchestrator and conversation-api |
conversation-api logs a consent/auth refusal |
2 | Customer hasn't consented to be messaged (or GET /auth/state methods regressed) — see customer-authentication.md |
async-agent: POST /share-codes → 401/403 |
3 | Missing agents:manage bearer on the share-code call — see async-agent CLAUDE.md |
async-agent: 200 but delivered: 0 |
3 | Provider accepted nothing — bad/unreachable phone number, or the SMS provider (SNS/Twilio) creds/region not configured |
Everything 200, still no SMS |
provider | Carrier filtering, sandbox/allow-list on the SMS provider, or a non-verified number in a provider sandbox |
4. If stage 1 still 400s after deploy¶
Re-check step 0. If SURFACE_APP_BASE_URL is still the internal http://…
value, the values change hasn't rolled out. As a last resort the code fix
(_safe_normalize_surface_base_url) tolerates the http entry, but that needs a
new conversation-api image; the faster path is to let ArgoCD apply the
values-kms-*.yaml change and restart conversation-api.
See also¶
- env-vars.md —
SURFACE_APP_BASE_URL/SURFACE_APP_URL - conversation-api.md — the validation authority
- customer-authentication.md — consent-to-be-messaged