Skip to content

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