Cookbook
Messaging Examples
Explore practical payload structures for each message kind supported by the messaging.send capability, along with the action types you can attach. Copy these examples and adapt them to your use case.
Permission: messaging:send in your manifest.json. The author label above outbound messages is set per-Instance by the admin via the Instance settings field messagingDisplayName; falls back to extension.manifest.name when blank. Extensions do not set or think about the author identity.
kind: 'text' — Plain text + reply actions
Plain conversational message. Optional actions array attaches quick-reply buttons, links, postbacks, or location requests.
const [send] = useMessaging()
await send({
"kind": "text",
"body": "Order approved ✓",
"actions": [
{
"type": "reply",
"label": "Got it",
"payload": "ACK"
},
{
"type": "reply",
"label": "Show details",
"payload": "DETAILS"
}
]
})
kind: 'image' — Embedded image + optional caption
Product photos, screenshots, visual help. url must respond with a Content-Type: image/* header (Sunco enforces; redirecting URLs like picsum.photos will fail). altText is optional — defaults to filename. Optional body renders a caption alongside the image.
const [send] = useMessaging()
await send({
"kind": "image",
"url": "https://cdn.example.com/products/widget-blue.jpg",
"altText": "Blue widget — model A24",
"body": "Here is the product you asked about:",
"actions": [
{
"type": "link",
"label": "View on site",
"url": "https://example.com/p/widget-a24"
}
]
})
kind: 'file' — Embedded file attachment
Return labels, receipts, NDAs, warranty paperwork. Sunco's fileMessage schema does NOT support actions — file messages can only carry an optional text caption.
const [send] = useMessaging()
await send({
"kind": "file",
"url": "https://cdn.example.com/receipts/order-12345.pdf",
"altText": "Receipt for order #12345",
"body": "Your receipt is attached."
})
kind: 'carousel' — Horizontally scrolling cards
The conversational-commerce primitive: product recommendations, size/color pickers, search results with images, multi-option selection.
- 1–10 items, each with required
title+ requiredactions(1–3 per item) - Item actions are a subset:
link+postbackonly (noreply, nolocationRequest— Sunco rejects those at the item level) - Carousel messages have NO message-level
actionsfield — actions live per-card - Use a separate
textmessage before the carousel if you need an intro
const [send] = useMessaging()
await send({
"kind": "carousel",
"items": [
{
"title": "Widget A24 — Blue",
"description": "In stock — ships in 1-2 days",
"imageUrl": "https://cdn.example.com/products/widget-blue.jpg",
"actions": [
{
"type": "link",
"label": "View",
"url": "https://example.com/p/widget-a24-blue"
},
{
"type": "postback",
"label": "Add to cart",
"payload": "add_to_cart:widget-a24-blue"
}
]
},
{
"title": "Widget A24 — Red",
"description": "Limited stock",
"imageUrl": "https://cdn.example.com/products/widget-red.jpg",
"actions": [
{
"type": "link",
"label": "View",
"url": "https://example.com/p/widget-a24-red"
},
{
"type": "postback",
"label": "Add to cart",
"payload": "add_to_cart:widget-a24-red"
}
]
}
],
"displaySettings": {
"imageAspectRatio": "horizontal"
}
})
Action types
Attach to message-level actions (text / image) or item-level actions (carousel). All actions share label + optional metadata (primitives only, ≤4KB total).
type: 'reply'
Inserts a visible user-reply bubble carrying payload back into the conversation, as if the user typed it. Mutually exclusive — a reply action cannot share an actions[] array with any other action type; the host validates and surfaces invalid_message if mixed.
{
"type": "reply",
"label": "Got it",
"payload": "ACK"
}
type: 'link'
Opens url in a new tab/window. No bubble inserted. Freely mixable with other non-reply actions.
{
"type": "link",
"label": "View on site",
"url": "https://example.com/p/widget-a24"
}
type: 'postback'
Fires a server-side conversation:postback webhook to the Stackable backend carrying the full payload + any metadata. NO visible bubble. Extensions with the events:messaging permission receive postback events via useMessagingEvent — see the Events & Identity cookbook.
{
"type": "postback",
"label": "Add to cart",
"payload": "add_to_cart:widget-a24"
}
type: 'locationRequest'
Prompts the user to share device location. Response arrives as a separate location-type message inbound to the conversation. Freely mixable.
{
"type": "locationRequest",
"label": "Share location"
}