Skip to content

Add automatic reply tracking webhook - #4

Open
guillaumegay13 wants to merge 3 commits into
mainfrom
codex/reply-tracking
Open

guillaumegay13 wants to merge 3 commits into
mainfrom
codex/reply-tracking

Conversation

@guillaumegay13

@guillaumegay13 guillaumegay13 commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • add signed reply-tracking addresses and set Reply-To on outbound emails when REPLY_TRACKING_DOMAIN is configured
  • add POST /api/webhooks/replies to resolve inbound replies by tracked address or In-Reply-To / References
  • surface reply_received as Replied in History and as a first-class workflow history event

Verification

  • npm run lint
  • npm run build

Summary by cubic

Adds automatic, provider-agnostic reply tracking. Outbound emails set a signed tracked Reply-To, and a new webhook resolves inbound replies and records them as Replied in History.

  • New Features

    • Signed tracked Reply-To on outbound emails when REPLY_TRACKING_DOMAIN is set.
    • POST /api/webhooks/replies ingests replies, matching by tracked address or In-Reply-To / References.
    • Creates reply_received and optional reply_outcome contact events; shown as Replied in History and workflow events.
    • History API/UI, OpenAPI spec, and docs updated.
    • .env.example adds REPLY_TRACKING_SECRET and REPLY_TRACKING_DOMAIN.
  • Migration

    • Set REPLY_TRACKING_SECRET and REPLY_TRACKING_DOMAIN.
    • Configure your inbound provider/forwarder to POST normalized reply data to /api/webhooks/replies with workspace auth (JWT or API key).
    • If not configured, existing behavior is unchanged.

Written for commit 3acf71c. Summary will update on new commits.

@vercel

vercel Bot commented Mar 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
send-again Ready Ready Preview, Comment Mar 26, 2026 10:37am

Request Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 11 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="src/app/app/page.tsx">

<violation number="1" location="src/app/app/page.tsx:394">
P2: `normalizeHistoryEventType` only recognizes `"reply"`, so `reply_received` events are skipped when building the history index and reply-based conditions can fail to match.</violation>
</file>

<file name="src/app/api/webhooks/replies/route.ts">

<violation number="1" location="src/app/api/webhooks/replies/route.ts:262">
P2: Long idempotency keys can collapse `reply_outcome` into the same dedupe key as `reply_received`, causing outcome events to be skipped.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/app/app/page.tsx Outdated
return "delivery";
case "open":
return "open";
case "reply":

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: normalizeHistoryEventType only recognizes "reply", so reply_received events are skipped when building the history index and reply-based conditions can fail to match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/app/page.tsx, line 394:

<comment>`normalizeHistoryEventType` only recognizes `"reply"`, so `reply_received` events are skipped when building the history index and reply-based conditions can fail to match.</comment>

<file context>
@@ -384,6 +391,8 @@ function normalizeHistoryEventType(eventType: string): HistoryEventType | null {
       return "delivery";
     case "open":
       return "open";
+    case "reply":
+      return "reply";
     case "click":
</file context>
Suggested change
case "reply":
case "reply":
case "reply_received":
case "replyreceived":
Fix with Cubic

Comment thread src/app/api/webhooks/replies/route.ts Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

3 issues found across 11 files (changes from recent commits).

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="src/lib/openapi.ts">

<violation number="1">
P2: The description was narrowed from "Tracked event type" to "SES event type" and "Reply" was dropped from the examples, but this PR adds `Replied` as a first-class history event. Since reply events don't originate from SES, this description is now inaccurate and will confuse API consumers.</violation>
</file>

<file name="src/app/app/page.tsx">

<violation number="1">
P1: Removing `"reply"` from `HistoryEventType` breaks the ability to create workflow conditions that filter by reply events. The PR adds a reply-tracking webhook that produces `reply_received` events, but this change removes the only frontend type that could represent them in `HistoryCondition`. Consider keeping or replacing it (e.g., with `"reply_received"`).</violation>

<violation number="2">
P1: Removing the `"reply"` / `"replyreceived"` cases from `normalizeEventType` and the `Reply` entry from `HISTORY_EVENT_META` breaks display of `reply_received` events produced by the new webhook. `normalizeEventTypeKey` would convert `"reply_received"` → `"replyreceived"`, which the old switch case handled correctly by returning `"Reply"`. Now these events render unstyled with no label. Both the normalization case and the metadata entry should be retained.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/lib/openapi.ts
@@ -764,7 +764,7 @@ export const openapiSpec = {
},

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The description was narrowed from "Tracked event type" to "SES event type" and "Reply" was dropped from the examples, but this PR adds Replied as a first-class history event. Since reply events don't originate from SES, this description is now inaccurate and will confuse API consumers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/openapi.ts, line 164:

<comment>The description was narrowed from "Tracked event type" to "SES event type" and "Reply" was dropped from the examples, but this PR adds `Replied` as a first-class history event. Since reply events don't originate from SES, this description is now inaccurate and will confuse API consumers.</comment>

<file context>
@@ -161,7 +161,7 @@ export const openapiSpec = {
                   type: "string",
                   description:
-                    "Tracked event type (for example: Send, Delivery, Open, Reply, Click, Bounce, Complaint, Reject, DeliveryDelay, RenderingFailure, Subscription).",
+                    "SES event type (for example: Send, Delivery, Open, Click, Bounce, Complaint, Reject, DeliveryDelay, RenderingFailure, Subscription).",
                 },
                 timestamp: { type: "string", format: "date-time" },
</file context>
Suggested change
},
"Tracked event type (for example: Send, Delivery, Open, Click, Replied, Bounce, Complaint, Reject, DeliveryDelay, RenderingFailure, Subscription).",
Fix with Cubic

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