Discord
Send Discord messages via Webhook API
The Discord channel is provided by @betternotify/discord. It sends messages through the Discord Webhook API with support for text content, rich embeds, and per-message username/avatar overrides.
When to use Discord
Discord is the right channel when you need to deliver notifications to a Discord server channel. Use it for deployment alerts, monitoring dashboards, CI/CD status updates, or any scenario where your team uses Discord for coordination. Messages support Markdown formatting and rich embed cards with structured fields, colors, images, and timestamps.
npm install @betternotify/discordSetup
import { createNotify } from '@betternotify/core';
import { discordChannel } from '@betternotify/discord';
const discord = discordChannel();
const rpc = createNotify({ channels: { discord } });Slots
Prop
Type
Send arguments
Prop
Type
Discord channels have no to field — the destination is determined by the webhook URL configured on the transport.
Full example
import { createNotify, createClient } from '@betternotify/core';
import { discordChannel, discordTransport } from '@betternotify/discord';
import { z } from 'zod';
const discord = discordChannel();
const rpc = createNotify({ channels: { discord } });
const catalog = rpc.catalog({
deployAlert: rpc
.discord()
.input(z.object({ service: z.string(), version: z.string(), status: z.string() }))
.body(({ input }) => `Deployed **${input.service}** v${input.version}`)
.embeds(({ input }) => [
{
title: `${input.service} v${input.version}`,
description: `Status: ${input.status}`,
color: input.status === 'healthy' ? 0x57f287 : 0xed4245,
timestamp: new Date().toISOString(),
},
]),
});
const notify = createClient({
catalog,
transportsByChannel: {
discord: discordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_URL! }),
},
});
await notify.deployAlert.send({
input: { service: 'api', version: '2.4.0', status: 'healthy' },
});Embeds
Embeds are Discord's rich content cards. Each embed can include:
{
title: 'Deployment Report',
description: 'All services deployed successfully',
url: 'https://dashboard.example.com/deploys/123',
color: 0x57f287,
timestamp: new Date().toISOString(),
footer: { text: 'BetterNotify', icon_url: 'https://example.com/icon.png' },
thumbnail: { url: 'https://example.com/thumb.png' },
image: { url: 'https://example.com/chart.png' },
author: { name: 'Deploy Bot', url: 'https://example.com', icon_url: 'https://example.com/bot.png' },
fields: [
{ name: 'Service', value: 'api', inline: true },
{ name: 'Version', value: '2.4.0', inline: true },
{ name: 'Region', value: 'us-east-1', inline: true },
],
}Discord allows up to 10 embeds per message.
Username and avatar overrides
Set defaults at the transport level and override per-route via channel slots:
const transport = discordTransport({
webhookUrl: process.env.DISCORD_WEBHOOK_URL!,
username: 'BetterNotify',
avatarUrl: 'https://example.com/default-avatar.png',
});
const catalog = rpc.catalog({
error: rpc
.discord()
.input(z.object({ message: z.string() }))
.body(({ input }) => input.message)
.username(() => 'Error Bot')
.avatarUrl(() => 'https://example.com/error-avatar.png'),
});Channel-level values take precedence over transport defaults.
Transport
Built-in: discordTransport
The package includes a ready-to-use transport that calls the Discord Webhook API via fetch:
import { discordTransport } from '@betternotify/discord';
const transport = discordTransport({
webhookUrl: process.env.DISCORD_WEBHOOK_URL!,
wait: true, // get message ID back from Discord
username: 'Bot', // default display name
});Set wait: true to receive the message ID in the response. When false (default), Discord returns 204 No Content for faster delivery.
Mock transport
Use mockDiscordTransport() in tests:
import { mockDiscordTransport } from '@betternotify/discord';
const transport = mockDiscordTransport();
// after sending...
console.log(transport.messages); // [{ body, embeds?, username?, avatarUrl?, id }]
transport.reset();Multi-transport
Use multiTransport from core for failover across multiple webhooks:
import { discordTransport } from '@betternotify/discord';
import { multiTransport } from '@betternotify/core/transports';
const transport = multiTransport({
strategy: 'failover',
transports: [
{ transport: discordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_PRIMARY! }) },
{ transport: discordTransport({ webhookUrl: process.env.DISCORD_WEBHOOK_FALLBACK! }) },
],
});