Skip to documentation
Documentation

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:

Shell
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/config

The 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:

JSON
{ "url": "https://your-app.example/review/123", "live": true }

The builtin maps delivery results like this:

Delivery stateTool result
accepted{ "success": true, "message_id": "…" }
unavailablesuccess: false with the unavailable reason as the error.
failedsuccess: false with a send-failed error.
unknownsuccess: 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:

JSON
{
  "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:

FieldRequirement
typeOne 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).
idRequired 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.
captionOptional string, 1–60 characters. For choice/confirm this is the ask shown on the card.
subcaptionOptional string, 1–80 characters.

Keiki adds presentationDelivery to the tool result:

JSON
{
  "presentationDelivery": {
    "state": "accepted",
    "messageId": "<provider-message-id>"
  }
}

The state is one of:

StateMeaning
acceptedThe provider accepted the card; this does not mean the recipient opened it.
unavailableThe card could not be sent. reason is addon_disabled, invalid_presentation, unsupported_channel, unsupported_presentation or line_not_eligible.
failedDelivery failed before acceptance.
unknownThe 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.