Skip to content

feat(chat): add push notification delivery endpoint and notify commands#96

Draft
dhairyashiil wants to merge 9 commits into
mainfrom
devin/1779514835-push-notification-delivery
Draft

feat(chat): add push notification delivery endpoint and notify commands#96
dhairyashiil wants to merge 9 commits into
mainfrom
devin/1779514835-push-notification-delivery

Conversation

@dhairyashiil

@dhairyashiil dhairyashiil commented May 23, 2026

Copy link
Copy Markdown
Member

Summary

Adds push notification delivery to the companion chat app so it can receive booking notifications dispatched by the /cal backend (PR #2927).

New endpoint: POST /api/notifications/deliver — authenticates via x-cal-delivery-secret header (HMAC + timingSafeEqual), validates the request body (including runtime validation of Slack teamId, payload fields, and timezone), then delegates to deliverNotifications() which fans out via Promise.allSettled to per-platform delivery modules. Returns 422 if the formatter crashes on bad input.

New commands: /cal notify on|off (Slack) and /notify on|off (Telegram) — toggle push notification subscriptions via the Cal.com API. Handles 404 specifically when the user isn't subscribed. Telegram rejects /notify in group chats (only works in private DM with the bot).

Files created:

  • apps/chat/lib/push-notifications/types.ts — shared DeliverResult type (with error field for retry/unsubscribe decisions)
  • apps/chat/lib/push-notifications/formatter.tsChatPushPayload type + buildPushCard() with URL validation, unknown notificationType fallback
  • apps/chat/lib/push-notifications/deliver-slack.ts — per-identifier Slack DM with structured SlackAPIError detection + logging
  • apps/chat/lib/push-notifications/deliver-telegram.ts — per-identifier Telegram DM with invalid chat detection + logging
  • apps/chat/lib/push-notifications/service.tsdeliverNotifications() fan-out with discriminated union narrowing + logging
  • apps/chat/app/api/notifications/deliver/route.ts — thin POST route (auth + parse + delegate) with pre-computed HMAC, payload validation, logging

Files modified:

  • apps/chat/lib/calcom/client.ts — 4 subscription methods (register/remove × Slack/Telegram)
  • apps/chat/lib/handlers/slack.tscase "notify": subcommand with 404 handling
  • apps/chat/lib/handlers/telegram.ts/notify command with group rejection + CalcomApiError import
  • apps/chat/lib/notifications.ts/notify added to both Slack and Telegram help cards
  • apps/chat/lib/env.tsCALCOM_DELIVERY_SECRET validation (hard fail prod, warn dev only)
  • apps/chat/.env.example — documented new env var

Review & Testing Checklist for Human

  • Confirm backend API contract: does /cal deliver Slack subscriptions as { identifier, teamId } or { identifier, deviceId }? Registration sends deviceId but delivery expects teamId — if the backend doesn't remap, every Slack delivery will silently fail
  • Confirm backend upserts on double-subscribe (running /cal notify on twice) — if it creates duplicates, users get duplicate notifications
  • Confirm backend populates data.url with the specific booking URL — currently falls back to https://app.cal.com/bookings
  • Verify CALCOM_DELIVERY_SECRET matches CALCOM_CHAT_DELIVERY_SECRET on the /cal backend before deploying
  • Test POST /api/notifications/deliver with valid/invalid secrets, malformed payloads (bad timezone, null hosts)
  • Test /cal notify on and /cal notify off in Slack — verify DM confirmation and 404 handling
  • Test /notify on in a Telegram group — should reject with "check your DMs" message
  • Test /notify on and /notify off in a private Telegram chat — verify subscription lifecycle

Notes

  • The 3 failing CI checks are pre-existing: Vercel preview deployments for extension/MCP apps + apply-labels-from-issue bot
  • Post-deployment: add /notify - Toggle booking push notifications on/off in BotFather (Settings → Edit Commands)
  • ChatPushPayload mirrors the type from /cal PR #2927 — notificationType is typed as string to gracefully handle future values with a fallback badge

Link to Devin session: https://app.devin.ai/sessions/53397cfd38d34318bf6b3694a8ff7721
Requested by: @dhairyashiil

- Add POST /api/notifications/deliver route with secret-based auth
- Add ChatPushPayload type and buildPushCard() card formatter
- Add per-identifier Slack and Telegram delivery modules
- Add deliverNotifications() fan-out service using Promise.allSettled
- Add /cal notify on|off Slack subcommand
- Add /notify on|off Telegram command
- Add 4 subscription management methods to Cal.com client
- Add CALCOM_DELIVERY_SECRET env var validation
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel

vercel Bot commented May 23, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: cal-companion-mcp.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@vercel

vercel Bot commented May 23, 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)
cal-companion-chat Ignored Ignored May 25, 2026 9:22pm

Request Review

- Validate Slack teamId at runtime in parseDeliverRequest
- Use structured SlackAPIError detection instead of string coercion
- Strip comma from weekday in formatPushTime
- Show reason for BOOKING_REJECTED (not just CANCELLED)
- Validate meetingUrl and data.url as https before rendering
- Extract DeliverResult to types.ts (no sibling imports)
- Split service.ts map by platform branch (remove type cast)
- Catch 404 on notify off for not-subscribed users
- Pre-compute secret HMAC at module load
- Guard env warning with NODE_ENV === development
- Reject /notify in Telegram groups with DM hint (#3)
- Validate payload fields (title, timeZone, start, end, hosts, attendees) in parseDeliverRequest (#4)
- Wrap deliverNotifications in try/catch, return 422 on formatter crash (#4)
- Add logging across route, service, and delivery modules (#5)
- Add /notify to Slack and Telegram help cards (#7)
- Add error field to DeliverResult for backend retry/unsubscribe decisions (#9)
- Change confirmation message from 'here' to 'via DM' (#10)
- Validate timeZone with Intl.DateTimeFormat before processing (#11)
- Add fallback badge for unknown notificationType (#12)
- Reject /notify in groups with 'check DMs' message (#13)
- Show non-HTTP meetingUrl as plain text instead of falling through to location
- Revert HMAC pre-computation — read secret per-request for rotation support
- Use missing[] array for CALCOM_DELIVERY_SECRET in production (consistent with other vars)
- Cap subscriptions array at 500 in parseDeliverRequest
- Add 409 handling on notify on for already-subscribed (Slack + Telegram)
- Add getLinkedUser check in Slack notify handler (consistent with other subcommands)
- fix(client): rename registerSlackSubscription input field deviceId → teamId
  to match the DeliverRequest type and parseDeliverRequest validator; the
  mismatch would have caused every Slack delivery request to be rejected
  with 400

- fix(slack): update call site to pass { identifier, teamId } (shorthand)
  instead of { identifier, deviceId: teamId }

- fix(route): use descriptive secretHmac/headerHmac variable names in
  verifyDeliverySecret instead of opaque a/b

- fix(route): add notificationType non-empty string validation and
  Date.parse validation for start/end in parseDeliverRequest; invalid
  dates would otherwise silently produce "Invalid Date" in the push card

- fix(route): log warn on JSON parse failure path
devin-ai-integration[bot]

This comment was marked as resolved.

dhairyashiil and others added 2 commits May 23, 2026 20:39
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Pass the actual error object to console.error so the message, stack
trace, and HTTP status are visible in logs — previously only a static
string label was logged, making the 403 root cause invisible.

Also add warn logs for registration failures in the hook and provider
so any future auth or scope issues surface immediately.
@dhairyashiil dhairyashiil marked this pull request as ready for review May 25, 2026 21:38
@dhairyashiil dhairyashiil marked this pull request as draft May 25, 2026 21:38

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/chat/lib/push-notifications/formatter.ts">

<violation number="1" location="apps/chat/lib/push-notifications/formatter.ts:31">
P2: `isHttpUrl` is too permissive for values that are interpolated into markdown/link fields. Use strict URL parsing so malformed or markdown-breaking URLs are rejected before building card links.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

};

function isHttpUrl(url: string): boolean {
return /^https?:\/\//i.test(url);

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: isHttpUrl is too permissive for values that are interpolated into markdown/link fields. Use strict URL parsing so malformed or markdown-breaking URLs are rejected before building card links.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chat/lib/push-notifications/formatter.ts, line 31:

<comment>`isHttpUrl` is too permissive for values that are interpolated into markdown/link fields. Use strict URL parsing so malformed or markdown-breaking URLs are rejected before building card links.</comment>

<file context>
@@ -0,0 +1,113 @@
+};
+
+function isHttpUrl(url: string): boolean {
+  return /^https?:\/\//i.test(url);
+}
+
</file context>

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