/

Engineering

You can now send Live Mini App Cards in iMessage

Evan

Ryan

No headings found on page

Start building
with Spectrum

Deploy AI agents
across every channel

Learn more about Spectrum

Preface

Preface

iMessage mini apps have always had the sheet view — tap a card, a full interactive surface slides up, and the user does their thing. That model is great for complex flows: multi-step checkouts, lengthy forms, anything that benefits from a dedicated screen.

But not every interaction needs a dedicated screen. Sometimes you just need a single tap: confirm a payment, approve a request, vote in a poll. For those cases, opening a sheet adds friction without adding value.

With this release, Spectrum now supports live mini app cards — interactive UI that renders directly inside the iMessage transcript. No tap-to-open, no sheet. The card is the app. The sheet view isn't going anywhere; you just have a second option now, tuned for quick, decisive interactions.

What "live" actually means

When you pass live: true to the app() call, Spectrum tells the iMessage provider to render your app extension's live UI inline—right in the chat bubble—instead of showing a static URL preview.

const card = app("<https://pay.example.com/invoice/4821>", { live: true })
await send(card)
const card = app("<https://pay.example.com/invoice/4821>", { live: true })
await send(card)
const card = app("<https://pay.example.com/invoice/4821>", { live: true })
await send(card)

On the Apple side, this maps to MSMessageLiveLayout—a layout class that tells Messages to instantiate your MSMessagesAppViewController directly inside the transcript bubble. Your view controller gets a presentationStyle of .transcript, and you render whatever UI fits. A payment confirmation button. A poll. A mini checkout form. The user interacts without ever leaving the conversation.

Multiple live messages from different apps can coexist in the same transcript simultaneously. Each gets its own view controller instance, so there are no conflicts.

Two ways to ship it

There are two paths depending on how much control you need:

  1. Spectrum App (Mini App Hub) — Photon's iMessage app, already approved by Apple, acts as a host for your mini apps. You define your app's behavior, Spectrum handles the iMessage extension plumbing. This is the fastest path to production.

  2. Custom mini app — Build your own iMessage extension. This gives you full control over the native experience via MSMessageLiveLayout, including custom sizing (override contentSizeThatFits(_:)), native animations, and device-specific behavior that goes beyond what a web layout can express.

The custom path is the right choice when your interaction demands pixel-level control or needs to feel indistinguishable from a first-party Apple experience. The Spectrum App path is the right choice for everything else.

Edit in place

The latest spectrum also introduces in-place mini app updates. When you send a mini app card, the returned message now includes a miniAppCardSession—a session handle that Spectrum uses for follow-up edits.

const card = app("<https://pay.example.com/invoice/4821>", { live: true })
const msg = await send(card)

// Later: update the same card in the conversation
await msg.update(
  app("<https://pay.example.com/invoice/4821?status=paid>", { live: true })
)
const card = app("<https://pay.example.com/invoice/4821>", { live: true })
const msg = await send(card)

// Later: update the same card in the conversation
await msg.update(
  app("<https://pay.example.com/invoice/4821?status=paid>", { live: true })
)
const card = app("<https://pay.example.com/invoice/4821>", { live: true })
const msg = await send(card)

// Later: update the same card in the conversation
await msg.update(
  app("<https://pay.example.com/invoice/4821?status=paid>", { live: true })
)

The card mutates in place. No new message, no duplicate bubble. This is the primitive that unlocks real-time use cases: a payment card that flips to "Paid" after the user confirms, a delivery tracker that updates as the package moves, a poll that shows results as votes come in.

Under the hood, the iMessage provider now uses @photon-ai/advanced-imessage ^1.0.0 to power this flow.

Graceful fallback

Not every conversation happens in iMessage. On platforms that don't support live layouts—SMS, Android, web chat, or even an older Apple device without the extension installed—Spectrum automatically falls back to a URL. The recipient gets a tappable link that opens the same experience in the browser.

You don't branch on platform. You write one app() call with live: true, and Spectrum handles the rest. The live experience is a progressive enhancement, not a hard requirement.

This also mirrors how MSMessageLiveLayout itself works: Apple requires an alternateLayout (an MSMessageTemplateLayout) for devices that can't render the live view. Spectrum sets that up automatically.

When to use this

Live mini apps make the most sense when:

  • The interaction is small and decisive — payments, confirmations, approvals, quick polls. Anything where opening a sheet adds friction that kills conversion.

  • Timeliness matters — the user needs to act now, in the flow of conversation, not later.

  • State changes are visible — the card should reflect what happened (paid, confirmed, expired) without the user having to tap in to check.

They make less sense for complex multi-step flows where a full-screen experience is genuinely better. The sheet view isn't going away—it's still the right tool for deeper interactions.

The bigger picture

This release is a step toward treating messaging platforms as first-class application surfaces, not just notification pipes. The card is no longer a link to the app. The card is the app.

Combined with in-place edits, developers now have a reactive primitive inside the conversation itself. Build the interaction once, let Spectrum negotiate the best rendering for each platform, and let the fallback URL catch everything else.

Links


Subscribe Photon Newsletter

Subscribe
Photon Newsletter