Skip to content

Commit 2fc2e12

Browse files
authored
feat(slack): added ephemeral message send tool, updated ci, updated docs (#3278)
* feat(slack): added ephemeral message send tool, updated ci, updated docs * added block kit support * upgrade turborepo * added wandConfig for slack block kit * fix generation type
1 parent 3fa4bb4 commit 2fc2e12

File tree

18 files changed

+465
-39
lines changed

18 files changed

+465
-39
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ jobs:
144144
tags: ${{ steps.meta.outputs.tags }}
145145
provenance: false
146146
sbom: false
147-
no-cache: true
148147

149148
# Build ARM64 images for GHCR (main branch only, runs in parallel)
150149
build-ghcr-arm64:
@@ -205,7 +204,6 @@ jobs:
205204
tags: ${{ steps.meta.outputs.tags }}
206205
provenance: false
207206
sbom: false
208-
no-cache: true
209207

210208
# Create GHCR multi-arch manifests (only for main, after both builds)
211209
create-ghcr-manifests:

.github/workflows/images.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ jobs:
9797
tags: ${{ steps.meta.outputs.tags }}
9898
provenance: false
9999
sbom: false
100-
no-cache: true
101100

102101
build-ghcr-arm64:
103102
name: Build ARM64 (GHCR Only)
@@ -144,11 +143,10 @@ jobs:
144143
tags: ${{ steps.meta.outputs.tags }}
145144
provenance: false
146145
sbom: false
147-
no-cache: true
148146

149147
create-ghcr-manifests:
150148
name: Create GHCR Manifests
151-
runs-on: blacksmith-8vcpu-ubuntu-2404
149+
runs-on: blacksmith-2vcpu-ubuntu-2404
152150
needs: [build-amd64, build-ghcr-arm64]
153151
if: github.ref == 'refs/heads/main'
154152
strategy:

.github/workflows/test-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ jobs:
110110
RESEND_API_KEY: 'dummy_key_for_ci_only'
111111
AWS_REGION: 'us-west-2'
112112
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
113-
run: bun run build
113+
run: bunx turbo run build --filter=sim
114114

115115
- name: Upload coverage to Codecov
116116
uses: codecov/codecov-action@v5

apps/docs/content/docs/en/tools/jira_service_management.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Create a new service request in Jira Service Management
116116
| `summary` | string | Yes | Summary/title for the service request |
117117
| `description` | string | No | Description for the service request |
118118
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
119-
| `requestFieldValues` | json | No | Custom field values as key-value pairs \(overrides summary/description if provided\) |
119+
| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) |
120120
| `requestParticipants` | string | No | Comma-separated account IDs to add as request participants |
121121
| `channel` | string | No | Channel the request originates from \(e.g., portal, email\) |
122122

apps/docs/content/docs/en/tools/slack.mdx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Slack
3-
description: Send, update, delete messages, add reactions in Slack or trigger workflows from Slack events
3+
description: Send, update, delete messages, send ephemeral messages, add reactions in Slack or trigger workflows from Slack events
44
---
55

66
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -59,7 +59,7 @@ If you encounter issues with the Slack integration, contact us at [help@sim.ai](
5959

6060
## Usage Instructions
6161

62-
Integrate Slack into the workflow. Can send, update, and delete messages, create canvases, read messages, and add reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
62+
Integrate Slack into the workflow. Can send, update, and delete messages, send ephemeral messages visible only to a specific user, create canvases, read messages, and add reactions. Requires Bot Token instead of OAuth in advanced mode. Can be used in trigger mode to trigger a workflow when a message is sent to a channel.
6363

6464

6565

@@ -80,6 +80,7 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format
8080
| `dmUserId` | string | No | Slack user ID for direct messages \(e.g., U1234567890\) |
8181
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
8282
| `threadTs` | string | No | Thread timestamp to reply to \(creates thread reply\) |
83+
| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. |
8384
| `files` | file[] | No | Files to attach to the message |
8485

8586
#### Output
@@ -146,6 +147,29 @@ Send messages to Slack channels or direct messages. Supports Slack mrkdwn format
146147
| `fileCount` | number | Number of files uploaded \(when files are attached\) |
147148
| `files` | file[] | Files attached to the message |
148149

150+
### `slack_ephemeral_message`
151+
152+
Send an ephemeral message visible only to a specific user in a channel. Optionally reply in a thread. The message does not persist across sessions.
153+
154+
#### Input
155+
156+
| Parameter | Type | Required | Description |
157+
| --------- | ---- | -------- | ----------- |
158+
| `authMethod` | string | No | Authentication method: oauth or bot_token |
159+
| `botToken` | string | No | Bot token for Custom Bot |
160+
| `channel` | string | Yes | Slack channel ID \(e.g., C1234567890\) |
161+
| `user` | string | Yes | User ID who will see the ephemeral message \(e.g., U1234567890\). Must be a member of the channel. |
162+
| `text` | string | Yes | Message text to send \(supports Slack mrkdwn formatting\) |
163+
| `threadTs` | string | No | Thread timestamp to reply in. When provided, the ephemeral message appears as a thread reply. |
164+
| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. |
165+
166+
#### Output
167+
168+
| Parameter | Type | Description |
169+
| --------- | ---- | ----------- |
170+
| `messageTs` | string | Timestamp of the ephemeral message \(cannot be used with chat.update\) |
171+
| `channel` | string | Channel ID where the ephemeral message was sent |
172+
149173
### `slack_canvas`
150174

151175
Create and share Slack canvases in channels. Canvases are collaborative documents within Slack.
@@ -682,6 +706,7 @@ Update a message previously sent by the bot in Slack
682706
| `channel` | string | Yes | Channel ID where the message was posted \(e.g., C1234567890\) |
683707
| `timestamp` | string | Yes | Timestamp of the message to update \(e.g., 1405894322.002768\) |
684708
| `text` | string | Yes | New message text \(supports Slack mrkdwn formatting\) |
709+
| `blocks` | json | No | Block Kit layout blocks as a JSON array. When provided, text becomes the fallback notification text. |
685710

686711
#### Output
687712

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
4+
import { checkInternalAuth } from '@/lib/auth/hybrid'
5+
import { generateRequestId } from '@/lib/core/utils/request'
6+
7+
export const dynamic = 'force-dynamic'
8+
9+
const logger = createLogger('SlackSendEphemeralAPI')
10+
11+
const SlackSendEphemeralSchema = z.object({
12+
accessToken: z.string().min(1, 'Access token is required'),
13+
channel: z.string().min(1, 'Channel ID is required'),
14+
user: z.string().min(1, 'User ID is required'),
15+
text: z.string().min(1, 'Message text is required'),
16+
thread_ts: z.string().optional().nullable(),
17+
blocks: z.array(z.record(z.unknown())).optional().nullable(),
18+
})
19+
20+
export async function POST(request: NextRequest) {
21+
const requestId = generateRequestId()
22+
23+
try {
24+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
25+
26+
if (!authResult.success) {
27+
logger.warn(`[${requestId}] Unauthorized Slack ephemeral send attempt: ${authResult.error}`)
28+
return NextResponse.json(
29+
{
30+
success: false,
31+
error: authResult.error || 'Authentication required',
32+
},
33+
{ status: 401 }
34+
)
35+
}
36+
37+
logger.info(
38+
`[${requestId}] Authenticated Slack ephemeral send request via ${authResult.authType}`,
39+
{ userId: authResult.userId }
40+
)
41+
42+
const body = await request.json()
43+
const validatedData = SlackSendEphemeralSchema.parse(body)
44+
45+
logger.info(`[${requestId}] Sending ephemeral message`, {
46+
channel: validatedData.channel,
47+
user: validatedData.user,
48+
threadTs: validatedData.thread_ts ?? undefined,
49+
})
50+
51+
const response = await fetch('https://slack.com/api/chat.postEphemeral', {
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
Authorization: `Bearer ${validatedData.accessToken}`,
56+
},
57+
body: JSON.stringify({
58+
channel: validatedData.channel,
59+
user: validatedData.user,
60+
text: validatedData.text,
61+
...(validatedData.thread_ts && { thread_ts: validatedData.thread_ts }),
62+
...(validatedData.blocks &&
63+
validatedData.blocks.length > 0 && { blocks: validatedData.blocks }),
64+
}),
65+
})
66+
67+
const data = await response.json()
68+
69+
if (!data.ok) {
70+
logger.error(`[${requestId}] Slack API error:`, data.error)
71+
return NextResponse.json(
72+
{ success: false, error: data.error || 'Failed to send ephemeral message' },
73+
{ status: 400 }
74+
)
75+
}
76+
77+
logger.info(`[${requestId}] Ephemeral message sent successfully`)
78+
79+
return NextResponse.json({
80+
success: true,
81+
output: {
82+
messageTs: data.message_ts,
83+
channel: validatedData.channel,
84+
},
85+
})
86+
} catch (error) {
87+
logger.error(`[${requestId}] Error sending ephemeral message:`, error)
88+
return NextResponse.json(
89+
{
90+
success: false,
91+
error: error instanceof Error ? error.message : 'Unknown error occurred',
92+
},
93+
{ status: 500 }
94+
)
95+
}
96+
}

apps/sim/app/api/tools/slack/send-message/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const SlackSendMessageSchema = z
1717
userId: z.string().optional().nullable(),
1818
text: z.string().min(1, 'Message text is required'),
1919
thread_ts: z.string().optional().nullable(),
20+
blocks: z.array(z.record(z.unknown())).optional().nullable(),
2021
files: RawFileInputArraySchema.optional().nullable(),
2122
})
2223
.refine((data) => data.channel || data.userId, {
@@ -63,6 +64,7 @@ export async function POST(request: NextRequest) {
6364
userId: validatedData.userId ?? undefined,
6465
text: validatedData.text,
6566
threadTs: validatedData.thread_ts ?? undefined,
67+
blocks: validatedData.blocks ?? undefined,
6668
files: validatedData.files ?? undefined,
6769
},
6870
requestId,

apps/sim/app/api/tools/slack/update-message/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const SlackUpdateMessageSchema = z.object({
1313
channel: z.string().min(1, 'Channel is required'),
1414
timestamp: z.string().min(1, 'Message timestamp is required'),
1515
text: z.string().min(1, 'Message text is required'),
16+
blocks: z.array(z.record(z.unknown())).optional().nullable(),
1617
})
1718

1819
export async function POST(request: NextRequest) {
@@ -57,6 +58,8 @@ export async function POST(request: NextRequest) {
5758
channel: validatedData.channel,
5859
ts: validatedData.timestamp,
5960
text: validatedData.text,
61+
...(validatedData.blocks &&
62+
validatedData.blocks.length > 0 && { blocks: validatedData.blocks }),
6063
}),
6164
})
6265

apps/sim/app/api/tools/slack/utils.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export async function postSlackMessage(
1111
accessToken: string,
1212
channel: string,
1313
text: string,
14-
threadTs?: string | null
14+
threadTs?: string | null,
15+
blocks?: unknown[] | null
1516
): Promise<{ ok: boolean; ts?: string; channel?: string; message?: any; error?: string }> {
1617
const response = await fetch('https://slack.com/api/chat.postMessage', {
1718
method: 'POST',
@@ -23,6 +24,7 @@ export async function postSlackMessage(
2324
channel,
2425
text,
2526
...(threadTs && { thread_ts: threadTs }),
27+
...(blocks && blocks.length > 0 && { blocks }),
2628
}),
2729
})
2830

@@ -220,6 +222,7 @@ export interface SlackMessageParams {
220222
userId?: string
221223
text: string
222224
threadTs?: string | null
225+
blocks?: unknown[] | null
223226
files?: any[] | null
224227
}
225228

@@ -242,7 +245,7 @@ export async function sendSlackMessage(
242245
}
243246
error?: string
244247
}> {
245-
const { accessToken, text, threadTs, files } = params
248+
const { accessToken, text, threadTs, blocks, files } = params
246249
let { channel } = params
247250

248251
if (!channel && params.userId) {
@@ -258,7 +261,7 @@ export async function sendSlackMessage(
258261
if (!files || files.length === 0) {
259262
logger.info(`[${requestId}] No files, using chat.postMessage`)
260263

261-
const data = await postSlackMessage(accessToken, channel, text, threadTs)
264+
const data = await postSlackMessage(accessToken, channel, text, threadTs, blocks)
262265

263266
if (!data.ok) {
264267
logger.error(`[${requestId}] Slack API error:`, data.error)
@@ -282,7 +285,7 @@ export async function sendSlackMessage(
282285
if (fileIds.length === 0) {
283286
logger.warn(`[${requestId}] No valid files to upload, sending text-only message`)
284287

285-
const data = await postSlackMessage(accessToken, channel, text, threadTs)
288+
const data = await postSlackMessage(accessToken, channel, text, threadTs, blocks)
286289

287290
if (!data.ok) {
288291
return { success: false, error: data.error || 'Failed to send message' }

0 commit comments

Comments
 (0)