mirror of
https://github.com/devkitshq/notifkit.git
synced 2026-10-19 18:24:52 +02:00
feat: promote slack to a dedicated native notification channel
This commit is contained in:
@@ -295,7 +295,7 @@ First-party providers ship for Resend and Firebase Cloud Messaging. Anything els
|
||||
|
||||
| | |
|
||||
| :------------------ | :------------------------------------------------------------------------------------- |
|
||||
| **Channels** | `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, `whatsapp` |
|
||||
| **Channels** | `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, `whatsapp`, `slack` |
|
||||
| **Targeting** | A user, a list of users, a segment, or a topic |
|
||||
| **Priorities** | `low`, `normal`, `high`, `critical` — separate stream lanes |
|
||||
| **Scheduling** | Future sends with `sendAt`, quiet-hours deferral, cancellation |
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."channel" ADD VALUE 'slack';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,13 @@
|
||||
"when": 1788779801992,
|
||||
"tag": "0003_skinny_daimon_hellstrom",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1788780502388,
|
||||
"tag": "0004_pretty_bruce_banner",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+11
-11
@@ -90,17 +90,17 @@ Because triple braces `{{{body}}}` preserve unescaped HTML, your agent in Claude
|
||||
|
||||
**Campaigns & Sending**
|
||||
|
||||
| Tool | What it does |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `send_campaign` | Send a template to a list of recipients across `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, or `whatsapp` with campaign tagging |
|
||||
| `send_notification` | Send to existing users, a segment, or a topic with `sendAt` scheduling and priority lanes |
|
||||
| `list_campaigns` | Recent campaign labels with size and last activity (supports `search`, `channel`, date range) |
|
||||
| `get_campaign_stats` | Sent, delivered, failed, opened, clicked, bounced, complained, unsubscribed, with rates |
|
||||
| `list_segments` | Every segment tag in use — the valid audiences to broadcast to |
|
||||
| `list_scheduled` | Sends queued for the future, including quiet-hours deferrals |
|
||||
| `get_delivery_logs` | Per-message detail with filters for template, channel, status, campaign, or user search |
|
||||
| `get_notification` | Status and delivery attempts for a specific task |
|
||||
| `cancel_notification` | Cancel a pending scheduled or quiet-hours deferred notification |
|
||||
| Tool | What it does |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `send_campaign` | Send a template to a list of recipients across `email`, `sms`, `push`, `webhook`, `telegram`, `discord`, `whatsapp`, or `slack` with campaign tagging |
|
||||
| `send_notification` | Send to existing users, a segment, or a topic with `sendAt` scheduling and priority lanes |
|
||||
| `list_campaigns` | Recent campaign labels with size and last activity (supports `search`, `channel`, date range) |
|
||||
| `get_campaign_stats` | Sent, delivered, failed, opened, clicked, bounced, complained, unsubscribed, with rates |
|
||||
| `list_segments` | Every segment tag in use — the valid audiences to broadcast to |
|
||||
| `list_scheduled` | Sends queued for the future, including quiet-hours deferrals |
|
||||
| `get_delivery_logs` | Per-message detail with filters for template, channel, status, campaign, or user search |
|
||||
| `get_notification` | Status and delivery attempts for a specific task |
|
||||
| `cancel_notification` | Cancel a pending scheduled or quiet-hours deferred notification |
|
||||
|
||||
**Templates** — `list_templates`, `get_template`, `upsert_template`, `delete_template`, `preview_template`, `render_template`
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const CHANNELS = [
|
||||
"telegram",
|
||||
"discord",
|
||||
"whatsapp",
|
||||
"slack",
|
||||
] as const;
|
||||
const CONTACT_CHANNELS = [
|
||||
"email",
|
||||
@@ -21,6 +22,7 @@ const CONTACT_CHANNELS = [
|
||||
"telegram",
|
||||
"discord",
|
||||
"whatsapp",
|
||||
"slack",
|
||||
] as const;
|
||||
const PRIORITIES = ["low", "normal", "high", "critical"] as const;
|
||||
|
||||
@@ -227,10 +229,10 @@ export function registerTools(server: McpServer, api: NotifkitApi): void {
|
||||
title: "Send a campaign to a list of addresses",
|
||||
description:
|
||||
"Send one template to a list of recipient destinations (email, SMS/phone, push tokens, webhook URLs, " +
|
||||
"Telegram chat ids, or Discord webhook URLs) given directly — the tool creates or updates a user " +
|
||||
"Telegram chat ids, Discord webhook URLs, WhatsApp phone numbers, or Slack channel/user IDs/webhook URLs) given directly — the tool creates or updates a user " +
|
||||
"record for each address, then sends, and tags every message with a campaign label so the results " +
|
||||
"can be read back later with get_campaign_stats. Supports 'email', 'sms', 'push', 'webhook', " +
|
||||
"'telegram', and 'discord' channels. Use this when the person supplies the recipients themselves " +
|
||||
"'telegram', 'discord', 'whatsapp', and 'slack' channels. Use this when the person supplies the recipients themselves " +
|
||||
"(a pasted list, a spreadsheet column). Always report the returned campaign label back to the user " +
|
||||
"— it is how they ask about results later. Suppressed addresses (previous unsubscribes, complaints, " +
|
||||
"hard bounces) are dropped automatically at send time, so the delivered count is normally lower than " +
|
||||
@@ -249,7 +251,7 @@ export function registerTools(server: McpServer, api: NotifkitApi): void {
|
||||
.enum(CONTACT_CHANNELS)
|
||||
.optional()
|
||||
.describe(
|
||||
"Channel to send on ('email', 'sms', 'push', 'webhook', 'telegram', or 'discord'). Defaults to 'email'.",
|
||||
"Channel to send on ('email', 'sms', 'push', 'webhook', 'telegram', 'discord', 'whatsapp', or 'slack'). Defaults to 'email'.",
|
||||
),
|
||||
recipients: z
|
||||
.array(z.string().min(1))
|
||||
@@ -257,8 +259,8 @@ export function registerTools(server: McpServer, api: NotifkitApi): void {
|
||||
.max(10000)
|
||||
.optional()
|
||||
.describe(
|
||||
"Recipient destination addresses: email addresses for email, E.164 phone numbers for sms, " +
|
||||
"device tokens for push, URLs for webhook, chat ids for telegram, or webhook URLs for discord.",
|
||||
"Recipient destination addresses: email addresses for email, E.164 phone numbers for sms/whatsapp, " +
|
||||
"device tokens for push, URLs for webhook, chat ids for telegram, webhook URLs for discord, or channel/user IDs for slack.",
|
||||
),
|
||||
emails: z
|
||||
.array(z.string().email())
|
||||
@@ -311,6 +313,10 @@ export function registerTools(server: McpServer, api: NotifkitApi): void {
|
||||
userRecord = { id: `telegram:${target}` };
|
||||
} else if (channel === "discord") {
|
||||
userRecord = { id: `discord:${target}` };
|
||||
} else if (channel === "whatsapp") {
|
||||
userRecord = { id: `whatsapp:${target}`, phone: target };
|
||||
} else if (channel === "slack") {
|
||||
userRecord = { id: `slack:${target}` };
|
||||
} else {
|
||||
userRecord = { id: `webhook:${target}` };
|
||||
}
|
||||
@@ -854,7 +860,7 @@ export function registerTools(server: McpServer, api: NotifkitApi): void {
|
||||
.optional()
|
||||
.describe(
|
||||
"Only list users who have a registered contact for this channel " +
|
||||
"(email, sms, push, webhook, telegram, discord).",
|
||||
"(email, sms, push, webhook, telegram, discord, whatsapp, slack).",
|
||||
),
|
||||
},
|
||||
annotations: { title: "List users", readOnlyHint: true },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# @notifkit/provider-slack
|
||||
|
||||
Slack transport for [notifkit](https://github.com/devkitshq/notifkit). Registers
|
||||
against the `webhook` channel and posts through either a Slack bot token
|
||||
against the `slack` channel and posts through either a Slack bot token
|
||||
(`chat.postMessage`) or a plain Incoming Webhook URL — whichever a recipient's
|
||||
stored contact target resolves to.
|
||||
|
||||
@@ -35,7 +35,7 @@ shape:
|
||||
| `appId` | Purely descriptive — shows up in log lines so you can tell multiple Slack app installations apart. Doesn't affect sending. |
|
||||
| `clientId` / `clientSecret` | Accepted, but **not** used to authenticate sends — see below. |
|
||||
|
||||
A recipient's `webhook` contact target (or a template's own `channel`
|
||||
A recipient's `slack` contact target (or a template's own `channel`
|
||||
override, or the transport's `webhookUrl` fallback) decides how the message
|
||||
is actually delivered:
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ export interface SlackTransportOptions {
|
||||
}
|
||||
|
||||
export class SlackTransport implements Transport {
|
||||
readonly channel = "webhook" as const;
|
||||
readonly channel = "slack" as const;
|
||||
readonly limits?: { limit: number; windowSeconds: number };
|
||||
|
||||
private readonly botToken?: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ export const NotificationChannelSchema = z.enum([
|
||||
"telegram",
|
||||
"discord",
|
||||
"whatsapp",
|
||||
"slack",
|
||||
]);
|
||||
export type NotificationChannel = z.infer<typeof NotificationChannelSchema>;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const RecipientProfileSchema = z.object({
|
||||
pushToken: z.string().optional(),
|
||||
telegram: z.string().optional(),
|
||||
discord: z.string().url().optional(),
|
||||
slack: z.string().optional(),
|
||||
locale: z.string().default("en"),
|
||||
timezone: z.string().default("UTC"),
|
||||
preferences: z.object({
|
||||
|
||||
@@ -31,6 +31,7 @@ export const ContactChannelSchema = z.enum([
|
||||
"telegram",
|
||||
"discord",
|
||||
"whatsapp",
|
||||
"slack",
|
||||
]);
|
||||
export type ContactChannel = z.infer<typeof ContactChannelSchema>;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export const channelEnum = pgEnum("channel", [
|
||||
"telegram",
|
||||
"discord",
|
||||
"whatsapp",
|
||||
"slack",
|
||||
]);
|
||||
|
||||
export const projects = pgTable("projects", {
|
||||
|
||||
@@ -172,9 +172,11 @@ export class AiWorker extends BaseWorker {
|
||||
? pending.recipient.telegram
|
||||
: pending.channel === "discord"
|
||||
? pending.recipient.discord
|
||||
: pending.channel === "push"
|
||||
? (pending.recipient.pushTokens?.[0] ?? pending.recipient.pushToken)
|
||||
: undefined;
|
||||
: pending.channel === "slack"
|
||||
? pending.recipient.slack
|
||||
: pending.channel === "push"
|
||||
? (pending.recipient.pushTokens?.[0] ?? pending.recipient.pushToken)
|
||||
: undefined;
|
||||
const resolvedDestination =
|
||||
destination ?? (pending.channel === "push" ? undefined : pending.recipientId);
|
||||
|
||||
|
||||
@@ -431,6 +431,7 @@ export class EnricherWorker extends BaseWorker {
|
||||
webhook: channel === "webhook" ? destination : undefined,
|
||||
telegram: channel === "telegram" ? destination : undefined,
|
||||
discord: channel === "discord" ? destination : undefined,
|
||||
slack: channel === "slack" ? destination : undefined,
|
||||
locale: profile.language ?? "en",
|
||||
timezone: profile.timezone ?? "UTC",
|
||||
preferences: {
|
||||
|
||||
@@ -1487,8 +1487,8 @@ describe("SlackTransport (Slack Provider)", () => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("rides the existing webhook channel rather than adding one of its own", () => {
|
||||
expect(new SlackTransport().channel).toBe("webhook");
|
||||
it("exposes the dedicated slack channel", () => {
|
||||
expect(new SlackTransport().channel).toBe("slack");
|
||||
});
|
||||
|
||||
it("constructs without options and defaults to a conservative rate ceiling", () => {
|
||||
|
||||
Reference in New Issue
Block a user