@fluojs/slack
v1.1.3
Published
Webhook-first, transport-agnostic Slack delivery core for Fluo with notifications integration.
Maintainers
Readme
@fluojs/slack
Webhook-first, transport-agnostic Slack delivery core for fluo. It provides a Nest-like module API, an injectable SlackService for standalone usage, and a first-party SlackChannel for @fluojs/notifications integration without assuming a Node-only SDK.
Table of Contents
Installation
npm install @fluojs/slack @fluojs/notificationsThis package follows the repo-wide Node.js 20+ install baseline reflected in published package metadata, while keeping its delivery contract transport-agnostic at runtime through explicit fetch-compatible boundaries.
When to Use
- When you want one package that can send Slack messages directly and also plug into
@fluojs/notifications. - When transport choice must stay explicit and portable across Node, Bun, Deno, and Cloudflare-compatible application boundaries.
- When Slack delivery should prefer incoming webhooks while still allowing richer API integrations through a custom transport contract.
- When configuration must enter through DI or explicit options instead of
process.envreads inside the package.
Quick Start
Register the module
import { Module } from '@fluojs/core';
import { SlackModule, createSlackWebhookTransport } from '@fluojs/slack';
@Module({
imports: [
SlackModule.forRoot({
defaultChannel: '#ops',
transport: createSlackWebhookTransport({
fetch: globalThis.fetch.bind(globalThis),
webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
}),
}),
],
})
export class AppModule {}Send Slack messages directly
import { Inject } from '@fluojs/core';
import { SlackService } from '@fluojs/slack';
@Inject(SlackService)
export class DeployNotifier {
constructor(private readonly slack: SlackService) {}
async announce(version: string) {
await this.slack.send({
text: `Deploy ${version} finished successfully.`,
});
}
}Common Patterns
Manual provider composition with createSlackProviders
createSlackProviders(...) is the supported manual-composition helper when applications need the same provider normalization outside SlackModule.forRoot(...).
import { Module } from '@fluojs/core';
import { createSlackProviders, createSlackWebhookTransport } from '@fluojs/slack';
@Module({
providers: [
...createSlackProviders({
defaultChannel: '#ops',
notifications: { channel: 'alerts' },
transport: createSlackWebhookTransport({
fetch: globalThis.fetch.bind(globalThis),
webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
}),
}),
],
exports: [],
})
export class SlackProvidersModule {}Behavioral contract notes:
- The helper preserves the same
SLACK,SLACK_CHANNEL, andSlackServicewiring thatSlackModule.forRoot(...)installs. createSlackProviders(...)applies the same option normalization asSlackModule.forRoot(...), including trimmed default channels, notification channel fallback, and transport ownership defaults.- The helper still requires an explicit
transport; it does not weaken the package's runtime-portable, no-implicit-env contract.
Standalone delivery with SlackService
Use SlackService when your application wants direct Slack delivery without routing through the notifications foundation.
SlackModule.forRootAsync({
inject: [ConfigService],
useFactory: (config) => ({
defaultChannel: config.slack.defaultChannel,
transport: createSlackWebhookTransport({
fetch: config.runtime.fetch,
webhookUrl: config.slack.webhookUrl,
}),
}),
});Behavioral contract notes:
SlackService.send(...)resolvesdefaultChannelbefore delivery.SlackService.sendMany(...)sends messages sequentially and supportscontinueOnErrorwhen callers need a batch result instead of fail-fast behavior.SlackService.send(...),SlackService.sendMany(...), andSlackService.sendNotification(...)honor already-aborted signals before provider handoff, and the same signal is propagated to transport calls.- The service initializes the configured transport during module bootstrap and closes factory-owned resources during application shutdown.
- Once shutdown starts,
SlackService.send(...)andSlackService.sendNotification(...)fail withSlackLifecycleErrorinstead of reusing or lazily creating transports; any in-flight factory-owned transport creation is awaited and closed by shutdown. SlackService.createPlatformStatusSnapshot()reports lifecycle, readiness, and transport ownership without requiring callers to reach into internal options.- The package never reads
process.envdirectly. All configuration must enter through explicit options or DI.
Integration with @fluojs/notifications
Inject SLACK_CHANNEL into NotificationsModule.forRootAsync(...) so the Slack package remains the only place that understands Slack-specific payload fields and recipient-to-channel translation.
import { Module } from '@fluojs/core';
import { NotificationsModule } from '@fluojs/notifications';
import {
SLACK_CHANNEL,
SlackModule,
createSlackWebhookTransport,
} from '@fluojs/slack';
@Module({
imports: [
SlackModule.forRoot({
transport: createSlackWebhookTransport({
fetch: globalThis.fetch.bind(globalThis),
webhookUrl: 'https://hooks.slack.com/services/T000/B000/XXXX',
}),
}),
NotificationsModule.forRootAsync({
inject: [SLACK_CHANNEL],
useFactory: (channel) => ({
channels: [channel],
}),
}),
],
})
export class AppModule {}Supported notification payload fields:
text,blocks,attachmentschannel,threadTs,replyBroadcastusername,iconEmoji,iconUrlmrkdwn,unfurlLinks,unfurlMedia,metadata
Behavioral contract notes:
- One notification dispatch maps to exactly one Slack destination. Use
payload.channelor a single entry inrecipients. - If
payload.channelis omitted,SlackService.sendNotification(...)uses the firstrecipientsentry or falls back todefaultChannel. - Notification metadata is merged from payload metadata, dispatch metadata, and subject/template markers before delivery.
- If a notification needs fan-out across multiple Slack destinations, call
sendMany(...)instead of one multi-recipient dispatch.
Webhook-first delivery with explicit fetch injection
Use createSlackWebhookTransport(...) when you want a portable first-party transport that only depends on a fetch-compatible HTTP boundary.
const transport = createSlackWebhookTransport({
fetch: runtime.fetch,
webhookUrl: slackWebhookUrl,
});
await slack.send({
blocks: [{ type: 'section', text: { type: 'mrkdwn', text: '*Deploy finished*' } }],
text: 'Deploy finished',
});For richer API integrations such as chat.postMessage, implement the exported SlackTransport contract and inject it through SlackModule.forRoot(...) or forRootAsync(...).
Behavioral contract notes:
- Passing
fetchexplicitly is the portable path and is recommended for all supported runtimes. For backward compatibility, omittingfetchfalls back toglobalThis.fetchwhen that ambient runtime API exists; runtimes withoutglobalThis.fetchfail fast withSlackConfigurationError. - The built-in webhook transport retries transient
408,429, and5xxfailures with bounded exponential backoff before surfacing an error. - Abort signals are passed to the injected
fetchboundary and cancel retry backoff without wrappingAbortErrorvalues asSlackTransportError. - Caller-visible
SlackTransportErrormessages omit raw upstream response bodies by default.
Intentional limitations
The Slack package intentionally does not:
- read credentials or webhook URLs from
process.env - ship a Node-only Slack SDK inside the shared root package boundary
- force one provider strategy beyond the webhook-first helper and exported transport contract
- translate one notification into multi-channel fan-out inside a single dispatch call
These limitations are part of the package contract so runtime choice, provider capability, and rollout strategy stay explicit at the application boundary.
Public API Overview
Core
SlackModule.forRoot(options)/SlackModule.forRootAsync(options)SlackModuleOptionsSlackAsyncModuleOptionscreateSlackProviders(options)SlackServiceSlackService.send(message, options)SlackService.sendMany(messages, options)SlackService.sendNotification(notification, options)SlackService.createPlatformStatusSnapshot()SlackChannelSLACKSLACK_CHANNEL
Service facade and result contracts
SlackSlackSendOptionsSlackSendManyOptionsSlackSendResultSlackSendBatchResultSlackSendFailure
Contracts and helpers
SlackMessageNormalizedSlackMessageSlackNotificationPayloadSlackNotificationDispatchRequestSlackBlockSlackAttachmentSlackTransportSlackTransportFactorySlackTransportContextSlackTransportReceiptSlackFetchLikeSlackFetchResponseSlackWebhookTransportOptionscreateSlackWebhookTransport(options)SlackTemplateRendererSlackTemplateRenderInputSlackTemplateRenderResult
Status and errors
createSlackPlatformStatusSnapshot(...)SlackPlatformStatusSnapshotSlackLifecycleStateSlackStatusAdapterInputSlackConfigurationErrorSlackLifecycleError: thrown by lifecycle-gated delivery, transport initialization, and owned-resource shutdown failures. Catch this error when sends can race with application teardown.SlackMessageValidationErrorSlackTransportError
Related Packages
@fluojs/notifications: Shared orchestration layer that consumesSLACK_CHANNEL.@fluojs/config: Recommended for resolving webhook URLs or tokens without direct environment access.@fluojs/event-bus: Useful when Slack notifications are one side effect among several event-driven workflows.
Example Sources
packages/slack/src/module.test.ts: Module registration,createSlackProviders(...)helper coverage, async wiring, webhook transport, and notifications integration examples.packages/slack/src/public-surface.test.ts: Public export and TypeScript contract verification.packages/slack/src/status.test.ts: Health/readiness contract examples.
