← All memos
May 22, 2026deliveryplatformClosed

Delivery acks the external-actions handler-execution topology and ships POST /api/external-actions/execute

Tagsexternal-actions, adr-0011, contracts, handler-execution

Delivery acks the external-actions handler-execution topology and ships POST /api/external-actions/execute

What

Ack on 2026-05-22-platform-external-actions-handler-execution-topology and the external-actions queue contract v1.2.0. No issues with the design. The HTTP-callback direction is the right call: a per-domain runner would fragment the single-runner guarantee and vendoring handler code into Platform's runner would couple Platform to every provider SDK.

Delivery's POST /api/external-actions/execute endpoint is live. The implementation covers:

lib/external-actions/verify-execute-request.ts provides verifyExecuteRequest, which reads the raw body, computes HMAC-SHA256 against EXTERNAL_ACTIONS_EXECUTE_SECRET_DELIVERY, and constant-time compares the result to the X-External-Actions-Signature header (accepted with or without the sha256= prefix, mirroring the dispatcher inbox convention). It also exports buildHandlerContext, which maps the runner's snake_case wire format to the SDK's HandlerContext type.

modules/external-actions-execute/routes/execute.routes.ts is the POST handler. It verifies the signature (401 on mismatch, 503 if the secret is unconfigured), looks up the registered handler via getExternalActionHandler(provider, action_kind), and returns terminal_failure if no handler is found so the runner dead-letters rather than retrying. It calls handler.execute(payload, context) and returns the HandlerResult as JSON with HTTP 200. A handler that throws unexpectedly returns terminal_failure rather than a 500, so the runner does not burn retry budget on a likely bug.

app/api/external-actions/execute/route.ts is the Next.js adapter.

The survey module import at the top of the route file (import "@/modules/survey") runs registerExternalActionHandler(quoSmsSendHandler) as a side effect at boot, so the SMS send handler is live before the first runner call arrives.

What remains

Platform needs EXTERNAL_ACTIONS_EXECUTE_URL_DELIVERY and EXTERNAL_ACTIONS_EXECUTE_SECRET_DELIVERY wired in its runner config for Delivery's endpoint to receive live callbacks. Once those env vars are set, the NPS V1 survey send is end-to-end.

Thread (4 memos)

May 22platformExternal Actions Queue handler execution; ADR-0011 amended and the queue contract bumped to v1.2.0, producer domains expose a Platform-callable /api/external-actions/execute endpointMay 22revenueRevenue acks the external-actions handler-execution topology; POST /api/external-actions/execute is roadmap work; external-actions-cancel retirement now unblocked by cancelExternalAction on the SDKMay 22salesSales acks external-actions handler-execution topology and queue contract v1.2.0; POST /api/external-actions/execute is roadmap work

View source on GitHub