iMessage Apps
Open HTTPS web pages as Keiki Vault iMessage app cards from agent tools.
Tools can open an HTTPS web page as an iMessage app card in a direct conversation. Recipients view cards in the Keiki Vault iMessage app.
Turn it on
In the agent form, enable Dynamic UI in the Abilities group. New agents start with the add-on off. Agents already on an active dedicated line bought through Keiki had it switched on automatically.
You can also enable it through the developer API. A key bound to an agent can
patch that agent directly; an organization-wide key includes agent_id:
curl -X PATCH -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "<agent-id>", "features": {"dynamicUi": true}}' \
https://<your-host>/api/v1/developer/configThe legacy imessageApps feature key is still honored as an alias.
Requirements
The agent must have an active dedicated iMessage line bought through Keiki and be replying in a direct iMessage conversation. Imported lines and shared or pool lines are not eligible. Cards are not sent in group conversations, on other channels, or during held-back, steered or detached turns.
Send a card
Use imessage_send_app
When the add-on is enabled, the imessage_send_app builtin is available. Call
it with an absolute HTTPS url and an optional live boolean:
{ "url": "https://your-app.example/review/123", "live": true }The builtin maps delivery results like this:
| Delivery state | Tool result |
|---|---|
accepted | { "success": true, "message_id": "…" } |
unavailable | success: false with the unavailable reason as the error. |
failed | success: false with a send-failed error. |
unknown | success: false; the error says the card may have been delivered. Do not retry automatically. |
Return a presentation from a tool
Any non-builtin tool can request a card by returning a presentation object.
This works for webhook, code, capability and MCP tools:
{
"status": "confirmation_required",
"url": "https://your-app.example/review/123",
"presentation": {
"type": "app",
"id": "review-123",
"url": "https://your-app.example/review/123",
"live": true,
"caption": "Review this change",
"subcaption": "Open the details"
}
}The presentation fields are:
| Field | Requirement |
|---|---|
type | One of "app", "choice" or "confirm". "app" delivers iMessage cards on eligible lines; choice/confirm render as buttons on Slack and Teams, and as a numbered list anywhere a text reply can reach (SMS, Telegram, WhatsApp DMs). |
id | Required stable intent ID, 1–128 characters. The same tool, ID and URL in one conversation reuse the delivery; use a new ID for a new card. |
url | "app" only. Required absolute HTTPS URL, at most 2,048 characters, without embedded credentials. |
live | "app" only. Optional boolean. |
options | "choice" only. 2–10 objects { "id": "…", "label": "…" } with unique ids; labels at most 80 characters. |
allowFreeText | "choice" only. Optional boolean; also accept a free-text reply. |
caption | Optional string, 1–60 characters. For choice/confirm this is the ask shown on the card. |
subcaption | Optional string, 1–80 characters. |
Keiki adds presentationDelivery to the tool result:
{
"presentationDelivery": {
"state": "accepted",
"messageId": "<provider-message-id>"
}
}The state is one of:
| State | Meaning |
|---|---|
accepted | The provider accepted the card; this does not mean the recipient opened it. |
unavailable | The card could not be sent. reason is addon_disabled, invalid_presentation, unsupported_channel, unsupported_presentation or line_not_eligible. |
failed | Delivery failed before acceptance. |
unknown | The outcome is uncertain; the card may have been delivered. Do not retry the same intent. |
Keep the page URL available in the result and show it as a normal link if delivery is unavailable, fails or is unknown. The sender, recipient and provider credentials are resolved by Keiki; tools should not provide them.
Choice and confirm presentations
A choice presents its options; a confirm asks a yes/no. Where a card
surface exists the presentation posts native buttons — Block Kit on Slack,
an Adaptive Card on Teams — whose click carries {presentationIntentId, ...answer}
back through Keiki's callback registrations. On channels without one it
compiles to a numbered list the recipient answers in text.
An accepted presentation is answerable: Keiki records each send separately
and binds native button callbacks to the exact intent row for that send. It
records the first matching answer — a selected option id, a confirmed
boolean, or free text where the choice allowed it. A click on a stale card, a
reply to an answered or expired presentation, or an answer the options did not
offer is not recorded. When a new choice/confirm delivers, the open
question it replaced is superseded; unanswered intents expire after 24 hours.
The answer itself still reaches the agent as a conversation event — a button press on card channels, the recipient's reply text on text channels — so the tool result should not wait for one.
What the recipient sees
The first time, the recipient installs Keiki Vault from the App Store. After
that, cards open their HTTPS page in the Keiki Vault iMessage app in Messages.
live: true uses Keiki Vault's Messages extension.
Opening a card is not an approval. The linked app remains responsible for authorization and any action the recipient takes there.
Telemetry
Keiki Vault reports when a card is opened, time on the card, load failures, and navigation bounces. Events are keyed only by the per-send token; they include no page content or URLs.