diff --git a/site/content/integrate/plugins/interactive-messages/_index.md b/site/content/integrate/plugins/interactive-messages/_index.md index 182f968b0d..0d665fadf3 100644 --- a/site/content/integrate/plugins/interactive-messages/_index.md +++ b/site/content/integrate/plugins/interactive-messages/_index.md @@ -21,6 +21,12 @@ To try it out, you can use this {{< newtabref href="https://github.com/matterpol ![image](poll.png) +## Markdown action buttons + +In addition to message-attachment buttons and menus, you can embed interactive affordances directly in a post's markdown body using `mmaction://` links backed by a `mm_blocks_actions` post prop. This is useful when a short message reads naturally with an inline "Approve" or "Reject" link and a full message attachment isn't warranted. + +See [markdown action buttons]({{< ref "/integrate/reference/markdown-actions" >}}) for the full schema, limits, and end-to-end flow. + ## Message buttons Add message buttons as `actions` in your integration {{< newtabref href="https://docs.mattermost.com/developer/message-attachments.html" title="message attachments" >}}. diff --git a/site/content/integrate/reference/markdown-actions/_index.md b/site/content/integrate/reference/markdown-actions/_index.md new file mode 100644 index 0000000000..b81631c727 --- /dev/null +++ b/site/content/integrate/reference/markdown-actions/_index.md @@ -0,0 +1,227 @@ +--- +title: "Markdown action buttons" +heading: "Use markdown action buttons" +description: "Markdown action buttons let an integration turn an inline post-markdown link into an action affordance. Clicking the link dispatches a post action to the integration's endpoint instead of navigating away, expanding interactivity beyond message attachments." +weight: 45 +--- + +{{}} +Markdown action buttons are one binding surface in a broader Interactive Messages framework under active development. Additional binding surfaces and action types are planned for future iterations. +{{}} + +Use markdown action buttons to add inline, in-text affordances to a post — without using a message attachment. They're useful when: + +- A short message reads naturally with an "Approve" or "Reject" inline link. +- An integration wants to mix narrative text and action affordances in the same post body. +- The visual weight of a full message attachment isn't warranted. + +For attachment-style buttons and menus, see [interactive messages]({{< ref "/integrate/plugins/interactive-messages" >}}). + +## How it works + +A markdown action has two parts: + +1. A markdown link in the post body using the `mmaction://` scheme, where the link host is the action ID: + + ```text + [Approve](mmaction://approve?ticket=ISS-101) + ``` + +2. A matching entry in the post's `props.mm_blocks_actions` registry that tells the server what to do when the link is clicked: + + ```json + { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + } + } + } + ``` + +The client renders the link as a button. Clicking it dispatches a request to the Mattermost server, which forwards the call to the integration's `url` along with merged query parameters and any server-side context. + +## Example post payload + +The following payload posts a message with two markdown action buttons. The body markdown references action IDs defined in `mm_blocks_actions`. + +```json +{ + "channel_id": "qmd5oqtwoibz8cuzxzg5ekshgr", + "message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)", + "props": { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + }, + "reject": { + "type": "external", + "url": "https://integration.example.com/hook/reject", + "context": {"project": "Demo Project"} + } + } + } +} +``` + +You can send this payload using the {{< newtabref href="https://api.mattermost.com/#operation/CreatePost" title="create post REST API" >}}, an [incoming webhook]({{< ref "/integrate/webhooks/incoming" >}}), or from a [plugin]({{< ref "/integrate/plugins/components/server" >}}). + +### Submit using the REST API + +```bash +curl -X POST $MM_URL/api/v4/posts \ + -H "Authorization: Bearer $BOT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "channel_id": "'$CHANNEL_ID'", + "message": "Ticket ISS-101 needs review: [Approve](mmaction://approve?ticket=ISS-101) [Reject](mmaction://reject?ticket=ISS-101)", + "props": { + "mm_blocks_actions": { + "approve": { + "type": "external", + "url": "https://integration.example.com/hook/approve", + "context": {"project": "Demo Project"} + }, + "reject": { + "type": "external", + "url": "https://integration.example.com/hook/reject", + "context": {"project": "Demo Project"} + } + } + } + }' +``` + +### Submit from a plugin + +A server-side plugin creates posts with markdown actions through the existing `API.CreatePost` / `API.UpdatePost` interfaces. The action `url` typically points at the plugin's own HTTP handler. + +```go +post := &model.Post{ + ChannelId: channelID, + UserId: p.botID, + Message: "Ticket ISS-101 needs review: " + + "[Approve](mmaction://approve?ticket=ISS-101) " + + "[Reject](mmaction://reject?ticket=ISS-101)", + Props: model.StringInterface{ + "mm_blocks_actions": map[string]any{ + "approve": map[string]any{ + "type": "external", + "url": fmt.Sprintf("/plugins/%s/inline_action/approve", manifest.Id), + "context": map[string]any{"project": "Demo Project"}, + }, + "reject": map[string]any{ + "type": "external", + "url": fmt.Sprintf("/plugins/%s/inline_action/reject", manifest.Id), + "context": map[string]any{"project": "Demo Project"}, + }, + }, + }, +} +_, err := p.API.CreatePost(post) +``` + +Plugin updates to `mm_blocks_actions` via `UpdatePost` are accepted only when the updated value passes validation. Removal of the `mm_blocks_actions` prop by non-integration sessions is restricted to prevent dropping or corrupting actions on posts owned by another integration. + +## Link syntax + +```text +[