Skip to content

feat: add multi-domain filters to webhooks#361

Open
KMKoushik wants to merge 2 commits intomainfrom
feat/webhook-domain-filter
Open

feat: add multi-domain filters to webhooks#361
KMKoushik wants to merge 2 commits intomainfrom
feat/webhook-domain-filter

Conversation

@KMKoushik
Copy link
Member

@KMKoushik KMKoushik commented Feb 27, 2026

Summary

  • Add webhook domain scoping with support for selecting multiple domains per endpoint (domainIds), where empty means all domains.
  • Update webhook dispatch logic so events only trigger for matching domains (or globally scoped endpoints), and validate selected domains belong to the team.
  • Extend webhook create/edit UI and TRPC inputs to configure domain filters and display selected domains on webhook details.

Testing

  • Not run (not requested).

Summary by cubic

Added multi-domain filters to webhooks so endpoints can target specific domains or all by leaving the selection empty. Events now dispatch only to matching domain-scoped or global endpoints.

  • New Features

    • Prisma: added Webhook.domainIds Int[] with default [].
    • Dispatch: filter active webhooks by domainId (includes global when empty).
    • Emit: pass domainId from domain events and SES hook parser.
    • API: TRPC create/update accept domainIds; validate domains belong to the team; deduplicate IDs.
    • UI: domain selector in create/edit; show selected domains in webhook details.
    • Tests: added TRPC router coverage to ensure domainIds propagate on create/update.
  • Migration

    • Run Prisma migrate to add domainIds.
    • No backfill needed; existing webhooks remain global (domainIds=[]).

Written for commit a01ad82. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Webhooks now support domain-specific event filtering: select specific domains or "All domains" when creating or editing webhooks; webhook details show associated domains with badges and "+N more" indicators.
  • Tests
    • Added tests to verify domain selection is included in webhook create and update flows.

@vercel
Copy link

vercel bot commented Feb 27, 2026

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

Project Deployment Actions Updated (UTC)
unsend-marketing Ready Ready Preview, Comment Feb 27, 2026 4:11am

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 27, 2026

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: a01ad82
Status: ✅  Deploy successful!
Preview URL: https://aa25e32e.usesend.pages.dev
Branch Preview URL: https://feat-webhook-domain-filter.usesend.pages.dev

View logs

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Walkthrough

Adds domain-scoped webhook support by introducing domainIds (Int[] with default empty array) to the Webhook Prisma model and database via a migration. Propagates domainIds through API routes, service layer (create, update, emit) with validation that domains belong to the team and emission filtering by domain. Updates server-side SES hook and domain event emissions to include domainId in webhook payloads. Front-end changes add domain selection and display in webhook create, edit, and info UIs. Adds tests verifying domainIds flow in router handlers.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding domain-based filtering to webhooks. It is concise, specific, and clearly reflects the primary feature being introduced across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/app/(dashboard)/webhooks/webhook-update-dialog.tsx (1)

317-395: Consider extracting domain selection into a shared component.

The domain selection UI (lines 317-395) is nearly identical to the implementation in add-webhook.tsx (lines 323-401). Consider extracting this into a reusable DomainSelector component to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/`(dashboard)/webhooks/webhook-update-dialog.tsx around lines
317 - 395, Extract the repeated domain-selection UI into a reusable
DomainSelector component and replace the inline FormField render block with it:
create DomainSelector that accepts props { domains, selectedDomainIds, onChange
} (or accept form field callbacks and integrate with FormField) and encapsulates
the DropdownMenu, DropdownMenuTrigger/Content, DropdownMenuCheckboxItem list,
the "All domains" checkbox, selectedDomainsLabel logic, and handleToggleDomain
behavior; then update both webhook-update-dialog.tsx and add-webhook.tsx to use
<DomainSelector .../> (wired to form.control or field.onChange) to remove
duplication while preserving the existing behaviors and callbacks (FormField,
DropdownMenuCheckboxItem, handleToggleDomain, selectedDomainsLabel).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/app/`(dashboard)/webhooks/webhook-update-dialog.tsx:
- Around line 317-395: Extract the repeated domain-selection UI into a reusable
DomainSelector component and replace the inline FormField render block with it:
create DomainSelector that accepts props { domains, selectedDomainIds, onChange
} (or accept form field callbacks and integrate with FormField) and encapsulates
the DropdownMenu, DropdownMenuTrigger/Content, DropdownMenuCheckboxItem list,
the "All domains" checkbox, selectedDomainsLabel logic, and handleToggleDomain
behavior; then update both webhook-update-dialog.tsx and add-webhook.tsx to use
<DomainSelector .../> (wired to form.control or field.onChange) to remove
duplication while preserving the existing behaviors and callbacks (FormField,
DropdownMenuCheckboxItem, handleToggleDomain, selectedDomainsLabel).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2ed09e and 3404f38.

📒 Files selected for processing (8)
  • apps/web/prisma/schema.prisma
  • apps/web/src/app/(dashboard)/webhooks/[webhookId]/webhook-info.tsx
  • apps/web/src/app/(dashboard)/webhooks/add-webhook.tsx
  • apps/web/src/app/(dashboard)/webhooks/webhook-update-dialog.tsx
  • apps/web/src/server/api/routers/webhook.ts
  • apps/web/src/server/service/domain-service.ts
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/service/webhook-service.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/server/api/routers/webhook-domain-filter.trpc.test.ts (1)

77-115: Consider adding edge case tests.

The current tests verify that domainIds is passed through correctly, which is good. For more comprehensive coverage, consider adding tests for:

  • Empty domainIds: [] (global webhook behavior)
  • Omitted domainIds (should remain undefined in the service call)

This would help document the expected behavior for global vs. domain-scoped webhooks.

💡 Example additional test cases
it("passes empty domainIds for global webhooks", async () => {
  const caller = createCaller(getContext());

  await caller.create({
    url: "https://example.com/webhook",
    eventTypes: ["email.sent"],
    domainIds: [],
  });

  expect(mockWebhookService.createWebhook).toHaveBeenCalledWith(
    expect.objectContaining({
      domainIds: [],
    })
  );
});

it("omits domainIds when not provided", async () => {
  const caller = createCaller(getContext());

  await caller.create({
    url: "https://example.com/webhook",
    eventTypes: ["email.sent"],
  });

  expect(mockWebhookService.createWebhook).toHaveBeenCalledWith(
    expect.objectContaining({
      domainIds: undefined,
    })
  );
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/server/api/routers/webhook-domain-filter.trpc.test.ts` around
lines 77 - 115, Add two edge-case tests to the existing
webhook-domain-filter.trpc.test.ts: one that calls create (via
createCaller(getContext())) with domainIds: [] and asserts
mockWebhookService.createWebhook was called containing domainIds: [], and
another that calls create without providing domainIds and asserts the service
call contains domainIds: undefined; follow the pattern used in the existing
tests (use expect.objectContaining and the same caller/getContext setup) and
mirror the existing assertions for update/create so the new tests reference
mockWebhookService.createWebhook and createCaller/getContext.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/server/api/routers/webhook-domain-filter.trpc.test.ts`:
- Around line 77-115: Add two edge-case tests to the existing
webhook-domain-filter.trpc.test.ts: one that calls create (via
createCaller(getContext())) with domainIds: [] and asserts
mockWebhookService.createWebhook was called containing domainIds: [], and
another that calls create without providing domainIds and asserts the service
call contains domainIds: undefined; follow the pattern used in the existing
tests (use expect.objectContaining and the same caller/getContext setup) and
mirror the existing assertions for update/create so the new tests reference
mockWebhookService.createWebhook and createCaller/getContext.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3404f38 and a01ad82.

📒 Files selected for processing (2)
  • apps/web/prisma/migrations/20260227040924_add_webhook_domain_filters/migration.sql
  • apps/web/src/server/api/routers/webhook-domain-filter.trpc.test.ts

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.

1 participant