Cookbook
Messaging Examples
Explore complete payload structures for every supported message kind and action type—copy these examples directly into your extension and adapt them for 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"
}