Skip to content

feat(notifications): add Discord provider - #637

Open
FindMalek wants to merge 2 commits into
databuddy-analytics:stagingfrom
FindMalek:feat/notifications-discord-provider
Open

feat(notifications): add Discord provider#637
FindMalek wants to merge 2 commits into
databuddy-analytics:stagingfrom
FindMalek:feat/notifications-discord-provider

Conversation

@FindMalek

@FindMalek FindMalek commented Aug 20, 2026

Copy link
Copy Markdown

Refs #635

Summary

packages/notifications/TODO.md checked off Discord (along with Teams, Telegram, Google Chat) as implemented, but only slack/email/webhook existed in providers/ and in the NotificationChannel type. This adds a real Discord provider as a first, self-contained slice, and corrects the TODO.md checkboxes for the three channels that are still genuinely unimplemented.

Approach

  • providers/discord.ts: webhook-based DiscordProvider mirroring SlackProvider's shape (retry/timeout via BaseProvider, safe fetch via the SSRF guard). Posts a Discord embed (title/description/fields, capped to Discord's actual limits — 256/4096/25 fields/1024 per field value), with a color + footer shown only for non-normal priority, mirroring Slack's elevatedPriority context block.
  • providers/payload-utils.ts: pulled truncate/isUserFacingMetadata/formatMetadataLabel out of slack.ts into a shared module so discord.ts doesn't duplicate them (identical behavior, slack.ts now just imports from here — no logic changes there).
  • types.ts: NotificationChannel gains "discord"; added DiscordPayload/DiscordEmbed/DiscordEmbedField, mirroring the existing Slack types.
  • client.ts / providers/index.ts: DiscordProvider wired into NotificationClient and exported from the barrel, same pattern as the other three providers.
  • TODO.md: Discord stays checked (now true); unchecked Teams/Telegram/Google Chat since those still don't exist in code.

Deliberately out of scope

Not touched: the alarms DB schema, RPC destination validation (packages/rpc/src/routers/alarms.tsSLACK_WEBHOOK_PATTERN-style validation, discriminated union), or the dashboard alarm-sheet UI. Wiring a new channel into the Alarms feature end-to-end (schema + validation + UI) is a bigger, separate surface than the provider primitive itself — happy to open that as a follow-up once this lands, rather than bundling a large multi-layer change into one PR.

Verification

cd packages/notifications && bun run test
# 66 pass, 0 fail (new discord.test.ts: embed building + send success/failure/misconfigured)

cd packages/notifications && bun run check-types
# clean

bunx ultracite check packages/notifications/src/providers/discord.ts packages/notifications/src/providers/payload-utils.ts packages/notifications/src/providers/slack.ts packages/notifications/src/providers/index.ts packages/notifications/src/client.ts packages/notifications/src/types.ts
# Checked 6 files. No fixes applied.

Full monorepo turbo run check-types and turbo run test also ran clean via the repo's pre-commit/pre-push hooks.

Disclosure

I used Claude Code to help write the provider (following SlackProvider's existing shape) and its tests. I read every changed line, ran the test suite and type-check myself, and manually checked Discord's actual embed field limits (256/4096/25 fields/1024 per value) against the API docs before encoding them.


Summary by cubic

Add a webhook-based Discord provider to packages/notifications and wire it into NotificationClient. Previously only Slack/email/webhook were supported; now NotificationChannel includes "discord" and the client can send rich embeds to Discord, enforcing per-field caps and Discord’s 6000‑character aggregate embed limit to avoid API errors.

  • Adds DiscordProvider with SSRF-guarded fetch via @databuddy/shared/ssrf-guard, retries/timeouts from BaseProvider, and embeds with truncation, 25-field cap, and color/footer for high/urgent priority.
  • Extracts shared helpers to providers/payload-utils.ts; SlackProvider now imports them with no behavior change.
  • Extends types with DiscordPayload/DiscordEmbed and exports the provider from the barrel; NotificationClient accepts a discord config.
  • Updates TODO.md to keep Teams/Telegram/Google Chat unchecked. Refs Linear docs(notifications): TODO.md claims Discord/Teams/Telegram/Google Chat providers exist — they don't #635.

Rollout

  • No Alarms schema/RPC/dashboard UI changes in this PR.
  • To use now, pass discord: { webhookUrl, username?, avatarUrl?, retries?, retryDelay?, timeout? } to NotificationClient.

Written for commit 07b65f0. Summary will update on new commits.

Review in cubic

TODO.md checked off Discord/Teams/Telegram/Google Chat as implemented, but
only Slack/email/webhook existed in providers/ and NotificationChannel.
Adds a real Discord provider as a first, self-contained slice; corrects
the TODO.md checkboxes for the three still-unimplemented channels.

- providers/discord.ts: webhook-based DiscordProvider mirroring
  SlackProvider, posting an embed (title/description/fields, with a
  priority color + footer for non-normal priority)
- providers/payload-utils.ts: truncate/isUserFacingMetadata/
  formatMetadataLabel extracted out of slack.ts so Discord doesn't
  duplicate them; slack.ts now imports from here
- types.ts: NotificationChannel gains "discord"; DiscordPayload/
  DiscordEmbed/DiscordEmbedField added
- client.ts, providers/index.ts: DiscordProvider wired in and exported

Deliberately not touched: the alarms DB schema, RPC destination
validation (packages/rpc/src/routers/alarms.ts), and the dashboard
alarm-sheet UI. Wiring a new channel into the alarms feature end-to-end
is a separate, larger surface — a natural follow-up once this provider
itself lands.

Refs databuddy-analytics#635
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

@FindMalek is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
vercel Bot temporarily deployed to Preview – documentation August 20, 2026 17:37 Inactive
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
documentation Skipped Skipped Aug 20, 2026 5:43pm

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a6ab5712-c000-455c-8541-738c7618a8f9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a webhook-based Discord notification provider and integrates it with NotificationClient.

  • Builds Discord embeds with component and aggregate character limits.
  • Uses the existing retry, timeout, and SSRF-safe request path.
  • Extracts shared Slack/Discord payload formatting helpers.
  • Adds Discord provider tests and corrects the notification roadmap.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/notifications/src/providers/discord.ts Implements bounded Discord embed construction and webhook delivery through the shared provider request lifecycle.
packages/notifications/src/client.ts Registers an optional Discord provider using the same default configuration pattern as existing providers.
packages/notifications/src/providers/payload-utils.ts Centralizes metadata filtering, labeling, and truncation helpers shared with Slack.
packages/notifications/src/tests/providers/discord.test.ts Covers embed limits, metadata filtering, priority styling, configuration errors, and webhook responses.
packages/notifications/src/types.ts Adds Discord to the notification channel union and defines its payload and embed types.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant Client as NotificationClient
  participant Discord as DiscordProvider
  participant Guard as SSRF-safe fetch
  participant API as Discord Webhook API
  Caller->>Client: sendToChannel("discord", payload)
  Client->>Discord: send(payload)
  Discord->>Discord: build bounded embed
  Discord->>Guard: POST webhook with retry/timeout
  Guard->>API: validated request
  API-->>Guard: HTTP response
  Guard-->>Discord: response
  Discord-->>Client: NotificationResult
  Client-->>Caller: NotificationResult
Loading

Reviews (3): Last reviewed commit: "fix(notifications): enforce Discord's ag..." | Re-trigger Greptile

Comment on lines +42 to +48
return {
title: truncate(payload.title, MAX_TITLE_LENGTH),
description: truncate(payload.message, MAX_DESCRIPTION_LENGTH),
...(fields.length > 0 && { fields }),
...(elevatedPriority && {
color: PRIORITY_COLORS[elevatedPriority],
footer: { text: `Priority: ${elevatedPriority.toUpperCase()}` },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Aggregate embed limit is unenforced

When a notification contains a near-limit description and multiple long metadata fields, these independently valid components exceed Discord's 6,000-character aggregate embed limit, causing Discord to reject the webhook and the alert to remain undelivered after retries.

Comment on lines +4 to +5
export type { DiscordProviderConfig } from "./discord";
export { DiscordProvider } from "./discord";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Discord extends the provider barrel

These exports expose the new Discord provider through an index-file barrel despite the repository's direct-import requirement, extending the prohibited indirection and making provider ownership less explicit.

Context Used: Basic guidelines for the project so vibe coders do... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Greptile flagged that per-component caps alone aren't enough: Discord
rejects an embed whose title + description + every field's name/value +
footer text sum past 6000 characters, even when each piece individually
respects its own limit (25 fields at the max 1024 chars each alone sums
to 25,600). Track a running total while building fields and stop adding
more once the next one would push the embed over the aggregate limit.
@vercel
vercel Bot temporarily deployed to Preview – documentation August 20, 2026 17:43 Inactive
@FindMalek

Copy link
Copy Markdown
Author

Fixed the real one, pushing back gently on the other:

Aggregate 6000-char limit — fixed in 07b65f0. Good catch, this was a genuine gap: the per-component caps (256/4096/25 fields/1024 per value) don't prevent the sum from exceeding Discord's actual 6000-char aggregate embed limit — 25 fields at max value length alone would be 25,600 chars. Now tracking a running total while building fields and stopping before the next field would push the embed over 6000. Added a test that constructs a payload designed to hit the aggregate cap before the field-count cap and asserts the total stays ≤ 6000.

Barrel export — leaving as-is. I don't think there's actually a direct-import rule here: biome.json has performance.noBarrelFile: "off" repo-wide, and providers/index.ts was already a barrel exporting Slack/Email/Webhook before this PR (with a biome-ignore-all lint/performance/noBarrelFile comment confirming it's intentional). I added DiscordProvider/DiscordProviderConfig to that same existing barrel, consistent with how the other three providers are already exported — happy to change it if there's a rule I'm missing that isn't captured in the lint config.

@FindMalek

Copy link
Copy Markdown
Author

@greptile review

@izadoesdev
izadoesdev deleted the branch databuddy-analytics:staging August 21, 2026 09:05
@izadoesdev izadoesdev closed this Aug 21, 2026
@izadoesdev izadoesdev reopened this Aug 21, 2026
@FindMalek

Copy link
Copy Markdown
Author

@izadoesdev this is ready for review whenever you have a chance — CI is green aside from the Vercel preview checks, which need a team member to authorize the deploy (outside my permissions as an external contributor).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants