Slack
Channels

Slack

Send Slack messages via Web API

The Slack channel is provided by @betternotify/slack. It sends messages through the Slack Web API with support for text, Block Kit, threading, and file uploads.

When to use Slack

Slack is the right channel when you need to deliver notifications to teams or channels inside Slack. Use it for deploy alerts, incident notifications, automated reports, or any scenario where your audience lives in Slack workspaces. Messages support rich formatting via Block Kit and optional file attachments.

npm install @betternotify/slack

Setup

import { createNotify } from '@betternotify/core';
import { slackChannel } from '@betternotify/slack';

const slack = slackChannel();

const rpc = createNotify({ channels: { slack } });

Slots

Prop

Type

Send arguments

Prop

Type

Full example

import { createNotify, createClient } from '@betternotify/core';
import { slackChannel, slackTransport } from '@betternotify/slack';
import { z } from 'zod';

const slack = slackChannel();
const rpc = createNotify({ channels: { slack } });

const catalog = rpc.catalog({
  deployAlert: rpc
    .slack()
    .input(z.object({ service: z.string(), version: z.string() }))
    .text(({ input }) => `${input.service} deployed ${input.version}`)
    .blocks(({ input }) => [
      { type: 'header', text: { type: 'plain_text', text: `Deploy: ${input.service}` } },
      { type: 'section', text: { type: 'mrkdwn', text: `Version *${input.version}* is now live.` } },
    ]),
});

const notify = createClient({
  catalog,
  transportsByChannel: {
    slack: slackTransport({ token: process.env.SLACK_BOT_TOKEN! }),
  },
});

await notify.deployAlert.send({
  to: '#releases',
  input: { service: 'api', version: 'v2.4.0' },
});

Block Kit

Use the blocks slot to build rich messages with Block Kit. The text field is always required as the notification fallback:

.text(({ input }) => input.title)
.blocks(({ input }) => [
  { type: 'header', text: { type: 'plain_text', text: input.title } },
  { type: 'section', text: { type: 'mrkdwn', text: input.body } },
  { type: 'divider' },
])

All block types are fully typed — you get autocomplete for header, section, image, divider, actions, context, input, video, file, and rich_text blocks.

Threading

Reply in a thread by passing threadTs:

await notify.deployAlert.send({
  to: '#releases',
  threadTs: '1234567890.123456',
  input: { service: 'api', version: 'v2.4.0' },
});

File uploads

Attach files using the file slot. The transport handles the 3-step Slack upload flow automatically:

const catalog = rpc.catalog({
  report: rpc
    .slack()
    .input(z.object({ title: z.string(), pdf: z.instanceof(Buffer) }))
    .text(({ input }) => `Report: ${input.title}`)
    .file(({ input }) => ({
      data: input.pdf,
      filename: `${input.title}.pdf`,
      title: input.title,
    })),
});

File uploads require the files:write scope on your Slack app.

Transport

Built-in: slackTransport

The package includes a ready-to-use transport that calls the Slack Web API via fetch:

import { slackTransport } from '@betternotify/slack';

const transport = slackTransport({
  token: process.env.SLACK_BOT_TOKEN!,
  defaultChannel: '#notifications', // optional fallback
});

Call transport.verify() to validate your bot token at startup via auth.test.

Mock transport

Use mockSlackTransport() in tests:

import { mockSlackTransport } from '@betternotify/slack';

const transport = mockSlackTransport();

// after sending...
console.log(transport.messages); // [{ text, to, blocks?, threadTs?, file?, id }]
transport.reset();

Multi-transport

Use multiTransport from core for failover across multiple Slack workspaces:

import { slackTransport } from '@betternotify/slack';
import { multiTransport } from '@betternotify/core/transports';

const transport = multiTransport({
  strategy: 'failover',
  transports: [
    { transport: slackTransport({ token: process.env.SLACK_PRIMARY! }) },
    { transport: slackTransport({ token: process.env.SLACK_FALLBACK! }) },
  ],
});

On this page