Skip to content

fix: notify assigned agent when customer replies via email#377

Open
akash2017sky wants to merge 4 commits into
mainfrom
fix/360-notify-agent-on-customer-reply
Open

fix: notify assigned agent when customer replies via email#377
akash2017sky wants to merge 4 commits into
mainfrom
fix/360-notify-agent-on-customer-reply

Conversation

@akash2017sky
Copy link
Copy Markdown
Collaborator

@akash2017sky akash2017sky commented May 9, 2026

Summary

When the email-poll path adds a comment to an existing ticket (action: comment_added), send an internal notification to the assigned agent so they don't have to discover the reply by refreshing the queue.

  • Recipient resolution: System.AssignedTo.uniqueName if it looks like an email, otherwise the new SUPPORT_TEAM_NOTIFY_EMAIL env var, otherwise log and skip.
  • Notification is fire-and-forget — wrapped so any mail send failure cannot block or fail the comment add.
  • Only triggers from handleThreadReply. Agent-UI comments use a different code path and never produce a notification.
  • Internal email goes on its own thread (no In-Reply-To header pointing at the customer's email), so it doesn't pollute the customer-facing conversation.
  • Same renderEmailBody pipeline as the on-ticket comment, so signature stripping is consistent between the two views the agent sees.

Fixes

Fixes #360

What this covers

Scenario Before After
Customer replies to a [ZapDesk #N]-tagged email; ticket has an assignee Comment added silently — agent only sees it on next page refresh Comment added; assignee receives [ZapDesk #N] New customer reply — "<title>" in their inbox with a View Ticket #N button
Customer replies; ticket is unassigned Same silent comment If SUPPORT_TEAM_NOTIFY_EMAIL is set, the team address gets the notification. Otherwise a [Notify] … skipping log line is emitted and the comment add still succeeds.
Assignee is a DevOps team / group identity (no @ in uniqueName) n/a (no notification existed) Treated as no individual assignee — falls through to SUPPORT_TEAM_NOTIFY_EMAIL.
Mail send fails (PAT issue, Graph 5xx, network) n/a Logged with ticket id; comment add still succeeds, poll still returns action: "comment_added".
Comment added by an agent in the ZapDesk UI n/a Unchanged — the UI uses a different DevOps code path; this notification never fires for agent-originated comments.

Files changed

  • src/lib/email-ingest.ts — capture the work item returned by addComment, extract System.AssignedTo + System.Title, fire-and-forget sendCustomerReplyNotification call. Adds a WorkItemFieldsResponse type for the PATCH response shape and a notifyAgentOfReply helper.
  • src/lib/email.ts — new sendCustomerReplyNotification exported. Uses generateMessageId(id, 'agent-notify') and deliberately omits inReplyTo so the agent thread is independent of the customer thread.
  • src/lib/email-templates.ts — new customerReplyNotificationTemplate reusing the existing layout/branding.
  • CLAUDE.md — documents the new optional SUPPORT_TEAM_NOTIFY_EMAIL env var.

New env var

Variable Purpose Required
SUPPORT_TEAM_NOTIFY_EMAIL Fallback recipient when a ticket has no individual assignee. Set to a distribution list like support@your-domain.com. No (without it, unassigned tickets just log a skip line)

Test plan

Local manual (verified)

  1. bun run dev, ensure MAIL_*, AZURE_DEVOPS_PAT, EMAIL_WEBHOOK_SECRET, MAIL_POLL_MAILBOX are set.
  2. Create or pick a ticket the PAT can write to. Assign it to yourself in DevOps.
  3. From a customer-domain address, send an email to the polled mailbox with Subject: [ZapDesk #N] testing notification.
  4. Trigger the poll:
    Invoke-RestMethod -Method Post -Uri http://localhost:3000/api/email/poll -Headers @{ "x-webhook-secret" = "<EMAIL_WEBHOOK_SECRET>" } | ConvertTo-Json -Depth 6
  5. Expected response: fetched: 1, ingested: 1, results[0].result.action: "comment_added", ticketId: N.
  6. Expected dev-server log:
    Added email reply to ticket #N from <customer>
    [Email] Customer reply notification sent for ticket #N to <assignee>
    
  7. Expected assignee inbox: new email subject [ZapDesk #N] New customer reply — "<original ticket title>", body shows Subject:, From:, the customer reply (signature-stripped) and a green View Ticket #N button.
  8. Confirm thread isolation: the notification email arrives in a different mail thread than the customer-facing emails for the same ticket.

Fallback path (verified earlier in the same session)

  1. Un-assign the ticket. Re-poll a fresh [ZapDesk #N] email.
  2. With SUPPORT_TEAM_NOTIFY_EMAIL set: notification arrives at the team address.
  3. Without it set: dev-server logs [Notify] No agent assigned and SUPPORT_TEAM_NOTIFY_EMAIL not set — skipping notification for ticket #N. Comment is still added — action: "comment_added" in the response.

Failure path (graceful degradation)

  1. Temporarily break MAIL_CLIENT_SECRET, send a [ZapDesk #N] email, re-poll.
  2. Expected: poll response still action: "comment_added"; comment still on the ticket; dev-server logs [Email] Failed to send customer reply notification for ticket #N: .... Restore secret afterwards.

UI-originated comments

  1. Add a comment from the ZapDesk ticket UI.
  2. Expected: no [Notify] or [Email] Customer reply notification log line. The notification path is scoped strictly to inbound email replies.

Automated checks

  • bun run typecheck — passes
  • bun run lint — passes (4 pre-existing warnings, unrelated)
  • bunx prettier --check on changed files — passes

Out of scope

  • Microsoft Teams / Slack / push notification channels (issue marks as future).
  • In-app banner / unread badge.
  • Per-user notification opt-out (issue accepts a global setting for v1).
  • Watchers / CC list (issue notes "future, if a watcher list is introduced").
  • Encrypted / S/MIME inbound mail.

🤖 Generated with Claude Code

Ayush and others added 4 commits May 9, 2026 20:09
When the email-poll path adds a comment to an existing ticket
(`action: comment_added`), send an internal notification to pull the
agent's attention rather than wait for them to spot the new comment in
the queue.

- Notify the ticket's `System.AssignedTo`. If the work item has no
  assignee (or the assignee is a team identity, not a user), fall back
  to `SUPPORT_TEAM_NOTIFY_EMAIL`. If neither, log and skip — comment
  add is unaffected.
- Fire-and-forget — the notify call is wrapped so any send failure
  cannot block or fail the ticket update.
- Only triggers from `handleThreadReply`. Comments added from the
  agent UI use a different code path and never produce a notification.
- Internal email goes on its own thread (no `In-Reply-To`), so it
  doesn't pollute the customer-facing conversation.
- Same `renderEmailBody` pipeline as the on-ticket comment, so
  signature stripping is consistent.

Closes #360

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adding SUPPORT_TEAM_NOTIFY_EMAIL pushed the description column past the
existing padding. Re-run prettier to widen the column so format:check
passes on CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Notify agents when a customer replies on an email-tagged ticket

2 participants