Flow Studio
Slack Messaging
Configuring the SendSlackMessage node — channels, Block Kit payloads, bot token credential resolution, and retry behavior.
Node Configuration
{
"nodeType": "SendSlackMessage",
"name": "notifyEngineering",
"config": {
"credentialId": 12,
"channelId": "#engineering-alerts",
"text": "Deployment $output.triggerDeploy.version completed successfully.",
"blocks": null,
"username": "BizFirstAI Bot",
"iconEmoji": ":robot_face:",
"threadTs": null,
"retryCount": 3,
"retryDelayMs": 1000
}
}
Configuration Fields
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | int | Yes | References a Slack bot token stored via ICredentialResolver |
channelId | string / expr | Yes | Channel ID (C01234) or name (#channel). Expressions allowed. |
text | string / expr | Yes* | Plain text message body. Required if blocks is null. |
blocks | array / expr | No | Block Kit JSON array. When set, overrides text as the primary payload. |
username | string | No | Override bot display name (requires chat:write.customize scope). |
iconEmoji | string | No | Override bot icon (requires chat:write.customize scope). |
threadTs | string / expr | No | Reply in thread — set to parent message ts. |
retryCount | int | No | Number of retry attempts on transient failure. Default: 2. |
retryDelayMs | int | No | Initial retry delay in milliseconds. Doubles on each attempt. |
Block Kit Payload
Pass a Block Kit JSON array via blocks to send rich Slack messages. The array can be a static value or a JavaScript expression building blocks from workflow data:
// Expression-built blocks
"blocks": `[
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Approval Required:* ' + $output.createInvoice.invoiceNumber
}
},
{
type: 'section',
fields: $output.createInvoice.lineItems.slice(0, 5).map(item => ({
type: 'mrkdwn',
text: '*' + item.description + '*\n$' + item.amount.toFixed(2)
}))
},
{
type: 'actions',
elements: [
{ type: 'button', text: { type: 'plain_text', text: 'Review' }, url: $context.workDeskUrl }
]
}
]`
SlackChannelAdapter — Executor
public class SlackChannelAdapter : IMessagingChannelAdapter
{
private readonly ICredentialResolver _credentials;
private readonly IHttpClientFactory _httpFactory;
public string ChannelType => "Slack";
public async Task<MessagingResult> SendAsync(
MessagingMessage message,
MessagingConfig config,
CancellationToken ct)
{
var token = await _credentials.GetPasswordAsync(config.CredentialId, ct);
var client = _httpFactory.CreateClient("SlackApi");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var payload = new
{
channel = config.ChannelId,
text = message.Text,
blocks = message.Blocks,
username = config.Username,
icon_emoji = config.IconEmoji,
thread_ts = config.ThreadTs
};
var response = await client.PostAsJsonAsync(
"https://slack.com/api/chat.postMessage", payload, ct);
var result = await response.Content.ReadFromJsonAsync<SlackApiResponse>(ct);
if (!result.Ok)
throw new MessagingException($"Slack error: {result.Error}");
return new MessagingResult
{
MessageId = result.Ts,
Channel = "slack",
Recipient = config.ChannelId,
SentAt = DateTimeOffset.UtcNow
};
}
}
Node Output
{
"messageId": "1716638400.123456",
"channel": "slack",
"sentAt": "2026-05-25T10:00:00Z",
"status": "delivered",
"recipient": "#engineering-alerts"
}
Threading: To reply in a thread, capture the
messageId (ts) from the first message's output and pass it as threadTs in subsequent nodes. This keeps related notifications grouped in a single Slack thread.
Required OAuth Scopes: Ensure the bot token has
chat:write at minimum. Add chat:write.customize to override username/icon. The token is stored via ICredentialResolver — never in node config.