Skip to content

Conversation tracking v2#840

Merged
elie222 merged 56 commits intomainfrom
feat/enhanced-reply-tracking
Oct 12, 2025
Merged

Conversation tracking v2#840
elie222 merged 56 commits intomainfrom
feat/enhanced-reply-tracking

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Oct 8, 2025

Add FYI and Actioned statuses

Summary by CodeRabbit

  • New Features

    • AI thread-status detection (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED) with rationale to drive labeling and tracking.
  • Improvements

    • Streamlined webhook & processing flows, unified rule/label handling, per-rule toggles, clearer onboarding, UI polish (toggles, tooltips, badges), improved Outlook/Gmail provider behaviors.
  • Bug Fixes

    • More resilient draft fetch/archive behavior and safer error handling for missing resources.
  • Tests & Docs

    • New E2E suites (Gmail/Outlook), AI-focused tests, and E2E README.
  • Removals

    • Cold-email settings UI/actions, several reply-tracker UIs/actions, Redis label storage, and TRACK_THREAD action.

@vercel
Copy link

vercel bot commented Oct 8, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Oct 12, 2025 11:41pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Centralizes webhook history processing into a shared processor, migrates cold-email and reply-tracker flows to rule/SystemType-driven logic, adds AI thread-status determination, expands EmailProvider APIs, removes TRACK_THREAD, updates Prisma schema/migrations, refactors UI/settings, and adds unit and E2E tests.

Changes

Cohort / File(s) Summary
Shared webhook processing & validation
apps/web/utils/webhook/process-history-item.ts, apps/web/app/api/google/webhook/process-history-item.ts, apps/web/app/api/outlook/webhook/process-history-item.ts, apps/web/app/api/google/webhook/process-history.ts, apps/web/app/api/outlook/webhook/process-history.ts, apps/web/utils/webhook/validate-webhook-account.ts, apps/web/app/api/google/webhook/types.ts, apps/web/app/api/outlook/webhook/types.ts
Centralized history processing into a shared processor, added webhook-account validation, changed handler signatures to option/validated forms, and moved provider-driven early exits and logging into the shared flow.
AI conversation-status & reply-tracker
apps/web/utils/ai/reply/determine-thread-status.ts, apps/web/utils/reply-tracker/conversation-status-config.ts, apps/web/utils/reply-tracker/handle-conversation-status.ts, apps/web/utils/ai/choose-rule/run-rules.ts, apps/web/utils/reply-tracker/outbound.ts, apps/web/utils/reply-tracker/handle-outbound.ts
Added aiDetermineThreadStatus, conversation-status types/config, determineConversationStatus + updateThreadTrackers, injected a conversation-tracking meta-rule into rule execution, and new outbound/handleOutbound orchestration.
Rule/SystemType refactor & onboarding
apps/web/utils/rule/consts.ts, apps/web/utils/category-config.tsx, apps/web/utils/actions/rule.ts, apps/web/utils/actions/rule.validation.ts, apps/web/utils/label-config.ts, apps/web/utils/rule/rule.ts
Replaced RuleName/SystemRule with SystemType-driven ruleConfig and helpers (getRuleConfig/getRuleName/getRuleLabel/getCategoryAction), added toggleRuleAction, safeCreateRule/runOnThreads, and moved label management to rule-centric APIs.
Cold-email → rule migration
apps/web/utils/cold-email/cold-email-rule.ts, apps/web/utils/cold-email/is-cold-email.ts, apps/web/utils/cold-email/cold-email-blocker-enabled.ts, apps/web/utils/actions/cold-email.ts, apps/web/prisma/migrations/...
Represented cold-email as a COLD_EMAIL rule, changed isColdEmail/aiIsColdEmail signatures to accept rule/prompt, added saveColdEmail/getColdEmailRule/isColdEmailRuleEnabled, removed legacy blocker actions, and added DB migrations to migrate settings to rules.
EmailProvider API additions
apps/web/utils/email/types.ts, apps/web/utils/email/google.ts, apps/web/utils/email/microsoft.ts, apps/web/utils/__mocks__/email-provider.ts, apps/web/utils/outlook/client.ts
Added provider methods (removeThreadLabels, deleteLabel, blockUnsubscribedEmail, isSentMessage, markReadMessage, getFolders), improved label/category handling, Outlook immutable-id support, and updated mocks.
Reply-tracker label helpers & removals
apps/web/utils/reply-tracker/label-helpers.ts, apps/web/utils/reply-tracker/label-helpers.test.ts, removed apps/web/utils/reply-tracker/inbound.ts, removed apps/web/utils/reply-tracker/check-previous-emails.ts, apps/web/utils/reply-tracker/draft-tracking.ts
Replaced labelMessageAsAwaitingReply with applyThreadStatusLabel (DB+provider-driven) and tests, removed legacy inbound/previous-email modules, and injected logger into draft-tracking functions.
TRACK_THREAD removal & action edits
apps/web/prisma/schema.prisma, apps/web/prisma/migrations/*, apps/web/utils/ai/actions.ts, apps/web/utils/action-display.tsx, apps/web/utils/action-item.ts, apps/web/utils/action-sort.ts, apps/web/app/(app)/**/assistant/*
Removed TRACK_THREAD from schema and code paths, updated enums/migrations, stripped TRACK_THREAD handling from AI actions, UI mappings, action inputs, and tests.
UI / settings updates
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx, apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx, removed apps/web/app/(app)/[emailAccountId]/assistant/settings/*, apps/web/app/(app)/[emailAccountId]/reply-zero/EnableReplyTracker.tsx, apps/web/app/(app)/[emailAccountId]/assistant/group/LearnedPatterns.tsx, apps/web/components/PlanBadge.tsx, apps/web/components/EmailMessageCell.tsx, apps/web/components/Toggle.tsx, apps/web/components/Tooltip.tsx, apps/web/components/ui/switch.tsx, apps/web/package.json
Added per-rule enabled toggles with optimistic updates, guarded editing for conversation-status rules, removed several settings UIs (AwaitingReply/SystemLabels/ColdEmail), added Radix Switch UI, and switched label/tooltip usage to SystemType helpers.
Redis label caching removal
removed apps/web/utils/redis/label.ts, apps/web/utils/actions/mail.ts, apps/web/utils/user/delete.ts
Deleted Redis label caching module and removed related save/delete calls and resource-deletion invocations.
AI/digest & assistant tooling updates
apps/web/app/api/ai/digest/route.ts, apps/web/app/api/resend/digest/route.ts, apps/web/utils/ai/assistant/chat.ts, apps/web/app/api/user/digest-settings/route.ts, apps/web/app/api/user/categorization-preferences/route.ts
Replaced RuleName with getRuleName/SystemType, expanded digest-supported system types, removed coldEmailBlocker from assistant tooling, and made categorization/digest settings rule-driven.
Tests & E2E additions
apps/web/__tests__/e2e/*, apps/web/__tests__/determine-thread-status.test.ts, apps/web/__tests__/ai-detect-recurring-pattern.test.ts, various updated unit tests
Added Gmail/Outlook E2E labeling tests and README, added aiDetermineThreadStatus unit tests, and updated unit tests to reflect provider and rule-shape changes and new ExecutedAction fields.
Misc Outlook/Gmail & draft fixes
apps/web/utils/outlook/draft.ts, apps/web/utils/outlook/mail.ts, apps/web/utils/outlook/label.ts, apps/web/app/(landing)/components/page.tsx
Made getDraft tolerant to 404s, preserved draft id on patch, made publish errors non-fatal during archive, and small layout tweaks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant WH as Webhook
  participant SP as SharedProcessor
  participant PR as EmailProvider
  participant DB as Prisma
  participant AI as LLM

  WH->>SP: processHistoryItem({messageId, threadId}, options)
  SP->>PR: getMessage(messageId)
  SP->>DB: find executedRule by threadId/messageId
  alt already processed or ignored
    SP-->>WH: return 200 (skip)
  else
    PR->>SP: isSentMessage(message)
    alt outbound
      SP->>SP: handleOutboundMessage(...)
      SP->>DB: update trackers / save executed actions
      SP-->>WH: done
    else inbound
      SP->>DB: check newsletter/unsubscribed
      alt unsubscribed
        SP->>PR: blockUnsubscribedEmail(messageId)
        SP-->>WH: done
      else has AI access
        SP->>AI: runRules / choose-rule (may invoke meta-rule)
        AI-->>SP: matched rule or conversation-status
        SP->>PR: perform actions (label/move/etc.)
        SP->>DB: save executed actions, update trackers
        SP-->>WH: done
      end
    end
  end
Loading
sequenceDiagram
  autonumber
  participant RR as runRules
  participant CR as prepareRulesWithMetaRule
  participant CS as determineConversationStatus
  participant PR as Provider
  participant AI as aiDetermineThreadStatus
  participant DB as Prisma

  RR->>CR: partition rules (conversation-status vs regular)
  CR-->>RR: rules + meta-rule (if present)
  RR->>RR: match rule
  alt meta-rule matched
    RR->>CS: determineConversationStatus(conversationRules, message,...)
    CS->>PR: getThreadMessages(threadId)
    PR-->>CS: messages[]
    CS->>AI: aiDetermineThreadStatus(threadMessages)
    AI-->>CS: {status, rationale}
    CS->>RR: specificRule + reason
    RR->>DB: save executed actions, update trackers/labels
  else regular rule matched
    RR->>DB: execute matched rule actions
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120–180 minutes

Possibly related PRs

Suggested reviewers

  • anakarentorosserrano-star
  • mosesjames7271-svg

Poem

🐇 I hopped through webhooks, rules, and labels bright,
Tucked cold emails into rules by moonlight.
The LLM peeked at threads and named their state,
Trackers, labels, migrations—neat and straight.
Tests thumped their feet — green lights into night.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title uses a generic version label “v2” rather than describing the specific enhancements (FYI and Actioned statuses) and thereby lacks detail about what changed; it gives a high-level indication but is too vague to fully convey the core updates. Consider renaming the title to explicitly reference the key changes, such as “Refactor conversation tracking to support FYI and Actioned statuses.”
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18e59aa and 7de7a5e.

📒 Files selected for processing (2)
  • apps/web/utils/webhook/validate-webhook-account.test.ts (1 hunks)
  • version.txt (1 hunks)

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
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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/web/utils/outlook/draft.ts (1)

17-28: Consider logging when converting errors to null.

The error handling correctly returns null for not-found scenarios, but adding debug/info logging would improve observability for troubleshooting draft-related issues.

Apply this diff to add logging:

   } catch (error) {
     // Handle 404 errors
     if (error instanceof Error && "code" in error && error.code === 404) {
+      logger.debug("Draft not found (404)", { draftId });
       return null;
     }

     // Handle Outlook's "object not found in store" error
     if (
       error instanceof Error &&
       error.message.includes("not found in the store")
     ) {
+      logger.debug("Draft not found in store", { draftId });
       return null;
     }

     throw error;
   }
apps/web/utils/ai/reply/determine-thread-status.ts (1)

1-98: Consider adding scoped logger for observability.

The implementation follows coding guidelines for AI features (separate prompts, Zod schema, proper structure), but adding a scoped logger would improve observability for debugging and monitoring.

Based on coding guidelines

Apply this diff to add logging:

 import { z } from "zod";
 import { createGenerateObject } from "@/utils/llms";
 import type { EmailAccountWithAI } from "@/utils/llms/types";
 import type { EmailForLLM } from "@/utils/types";
 import { getModel } from "@/utils/llms/model";
 import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
 import type { ThreadStatus } from "@/utils/reply-tracker/conversation-status-config";
 import { SystemType } from "@prisma/client";
+import { createScopedLogger } from "@/utils/logger";
+
+const logger = createScopedLogger("ai-determine-thread-status");

 export async function aiDetermineThreadStatus({
   emailAccount,
   threadMessages,
 }: {
   emailAccount: EmailAccountWithAI;
   threadMessages: EmailForLLM[];
 }): Promise<{ status: ThreadStatus; rationale: string }> {
+  logger.trace("Determining thread status", {
+    emailAccountId: emailAccount.id,
+    messageCount: threadMessages.length,
+  });
+
   const system = `You are an AI assistant that analyzes email threads to determine their current status.
apps/web/utils/actions/rule.ts (1)

788-877: Review error handling and cache invalidation.

The new toggle action has solid logic but consider these refinements:

  1. Error type inconsistency (Line 801): Uses generic Error instead of SafeError. For consistency with other actions in this file (e.g., lines 218, 367), consider using SafeError.

  2. Missing cache invalidation: Unlike other rule actions (e.g., lines 359-361, 395-399), this action doesn't call revalidatePath after toggling the rule. This could lead to stale UI state.

  3. Hardcoded AI instructions (Line 839): The instruction "Personal conversations with real people..." is hardcoded. Should this be configurable via statusConfig.description or similar?

Apply these refinements:

     if (!statusConfig) {
-      throw new Error(`Invalid system type: ${systemType}`);
+      throw new SafeError(`Invalid system type: ${systemType}`);
     }

Add cache invalidation after the enable/disable operations:

         }
       }
+
+      revalidatePath(prefixPath(emailAccountId, "/assistant"));
+      revalidatePath(prefixPath(emailAccountId, "/automation"));
+      revalidatePath(prefixPath(emailAccountId, "/reply-zero"));
     },
   );
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 115c390 and 339c03c.

📒 Files selected for processing (20)
  • apps/web/__tests__/determine-thread-status.test.ts (1 hunks)
  • apps/web/__tests__/outlook-operations.test.ts (2 hunks)
  • apps/web/app/api/ai/digest/route.ts (1 hunks)
  • apps/web/app/api/google/webhook/process-history-item.ts (2 hunks)
  • apps/web/app/api/outlook/webhook/process-history-item.ts (2 hunks)
  • apps/web/components/PlanBadge.tsx (2 hunks)
  • apps/web/prisma/schema.prisma (2 hunks)
  • apps/web/utils/actions/rule.ts (4 hunks)
  • apps/web/utils/actions/rule.validation.ts (1 hunks)
  • apps/web/utils/ai/actions.ts (1 hunks)
  • apps/web/utils/ai/reply/determine-thread-status.ts (1 hunks)
  • apps/web/utils/email/google.ts (1 hunks)
  • apps/web/utils/email/microsoft.ts (1 hunks)
  • apps/web/utils/email/types.ts (1 hunks)
  • apps/web/utils/label-config.ts (5 hunks)
  • apps/web/utils/outlook/draft.ts (1 hunks)
  • apps/web/utils/outlook/mail.ts (1 hunks)
  • apps/web/utils/reply-tracker/consts.ts (1 hunks)
  • apps/web/utils/reply-tracker/conversation-status-config.ts (1 hunks)
  • apps/web/utils/webhook/process-history-item.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (29)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/prisma/schema.prisma
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/reply-tracker/consts.ts
  • apps/web/utils/actions/rule.validation.ts
  • apps/web/prisma/schema.prisma
  • apps/web/utils/ai/actions.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/label-config.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/__tests__/outlook-operations.test.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/*.validation.ts

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Define Zod schemas for validation in dedicated files and use them for both client and server validation.

Define input validation schemas using Zod in the corresponding .validation.ts file. These schemas are used by next-safe-action (.schema()) and can also be reused on the client for form validation.

Files:

  • apps/web/utils/actions/rule.validation.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.validation.ts
  • apps/web/utils/actions/rule.ts
apps/web/prisma/schema.prisma

📄 CodeRabbit inference engine (.cursor/rules/prisma.mdc)

The Prisma schema file must be located at apps/web/prisma/schema.prisma

Files:

  • apps/web/prisma/schema.prisma
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/actions.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/actions.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
**/*.test.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
apps/web/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Place LLM-specific tests under apps/web/tests/

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
**/__tests__/**

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Place AI tests in the __tests__ directory and exclude them from the default test run (they use a real LLM)

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
apps/web/__tests__/**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/llm-test.mdc)

apps/web/__tests__/**/*.test.ts: Place all LLM-related tests under apps/web/tests/
Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"
Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests
Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")
Call vi.clearAllMocks() in a beforeEach for LLM tests
Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks
Create helper functions for common test data (e.g., getUser, getTestData) to reduce duplication
Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats
Use console.debug to log generated LLM content for inspection (e.g., console.debug("Generated content:\n", result.content))
Do not mock the actual LLM call in these tests; exercise real LLM integrations
Test both AI and non-AI paths, including cases where no AI processing is required
Prefer existing helpers from @/tests/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers

Files:

  • apps/web/__tests__/determine-thread-status.test.ts
  • apps/web/__tests__/outlook-operations.test.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/ai/digest/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/ai/digest/route.ts
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/components/PlanBadge.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use React Hook Form with Zod validation for form handling

Use the LoadingContent component to handle loading and error states consistently in data-fetching components.

Use PascalCase for components (e.g. components/Button.tsx)

Files:

  • apps/web/components/PlanBadge.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/components/PlanBadge.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/components/PlanBadge.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/components/PlanBadge.tsx
🧠 Learnings (7)
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod schemas for validation in dedicated files and use them for both client and server validation.

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use Zod schemas for validation on both client and server

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Always define a Zod schema for response validation

Applied to files:

  • apps/web/utils/actions/rule.validation.ts
📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.053Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/__tests__/determine-thread-status.test.ts
📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.053Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")

Applied to files:

  • apps/web/__tests__/determine-thread-status.test.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/__tests__/determine-thread-status.test.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/email/google.ts
🧬 Code graph analysis (12)
apps/web/utils/actions/rule.ts (6)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (62-113)
apps/web/utils/actions/rule.validation.ts (1)
  • toggleConversationStatusBody (210-218)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • CONVERSATION_STATUSES (17-50)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/rule/rule.ts (1)
  • safeCreateRule (46-112)
apps/web/__tests__/determine-thread-status.test.ts (2)
apps/web/__tests__/helpers.ts (2)
  • getEmail (25-42)
  • getEmailAccount (6-23)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-98)
apps/web/utils/ai/reply/determine-thread-status.ts (5)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/types.ts (1)
  • EmailForLLM (117-131)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • ThreadStatus (9-9)
apps/web/utils/ai/helpers.ts (2)
  • getUserInfoPrompt (9-35)
  • getEmailListPrompt (54-68)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
apps/web/utils/email/google.ts (2)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/gmail/label.ts (1)
  • GmailLabel (24-38)
apps/web/utils/email/types.ts (1)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/label-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (2)
  • FYI_LABEL_NAME (3-3)
  • ACTIONED_LABEL_NAME (4-4)
apps/web/utils/webhook/process-history-item.ts (13)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-225)
apps/web/utils/types.ts (1)
  • RuleWithActionsAndCategories (16-18)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/assistant/process-assistant-email.ts (1)
  • processAssistantEmail (20-34)
apps/web/utils/mail.ts (1)
  • emailToContent (72-101)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • runColdEmailBlocker (162-188)
apps/web/utils/digest/index.ts (1)
  • enqueueDigestItem (11-48)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
apps/web/utils/categorize/senders/categorize.ts (1)
  • categorizeSender (17-57)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • runRules (43-97)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (4)
  • NEEDS_REPLY_LABEL_NAME (1-1)
  • FYI_LABEL_NAME (3-3)
  • AWAITING_REPLY_LABEL_NAME (2-2)
  • ACTIONED_LABEL_NAME (4-4)
apps/web/app/api/google/webhook/process-history-item.ts (2)
apps/web/app/api/google/webhook/types.ts (2)
  • HistoryEventType (6-10)
  • HistoryEventType (12-13)
apps/web/app/api/google/webhook/process-label-removed-event.ts (1)
  • handleLabelRemovedEvent (23-107)
apps/web/app/api/outlook/webhook/process-history-item.ts (1)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/__tests__/outlook-operations.test.ts (2)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/email/microsoft.ts (1)
  • OutlookProvider (66-1246)
🪛 GitHub Actions: Run Tests
apps/web/utils/webhook/process-history-item.ts

[error] 13-13: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'. Does the file exist?

🪛 GitHub Check: test
apps/web/utils/webhook/process-history-item.ts

[failure] 13-13: app/api/sso/signin/route.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: app/api/google/webhook/process-history-item.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: utils/outlook/subscription-manager.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: utils/middleware.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: utils/api-auth.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: tests/outlook-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: tests/gmail-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 13-13: tests/ai-process-user-request.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (15)
apps/web/components/PlanBadge.tsx (1)

10-10: LGTM! Type-safe status comparison.

The refactor from string literal "APPLIED" to ExecutedRuleStatus.APPLIED improves type safety and aligns with Prisma best practices. The logic remains unchanged while reducing the risk of typos in status comparisons.

Also applies to: 74-81

apps/web/utils/ai/actions.ts (1)

154-171: Verify removal of reply tracking is intentional.

The reply action no longer calls coordinateReplyProcess, which means automatic reply tracking after sending replies has been removed. This appears to be a significant behavioral change where reply tracking must now be explicitly configured via the TRACK_THREAD action instead.

Please confirm this behavioral change is intentional and that users who previously relied on automatic reply tracking after replies are aware they need to add explicit TRACK_THREAD actions to their rules.

apps/web/__tests__/determine-thread-status.test.ts (1)

1-467: Excellent test coverage for AI thread status determination.

The test suite comprehensively covers various scenarios including:

  • Basic status types (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED)
  • Edge cases (FYI message with unanswered questions)
  • Multi-person threads from different perspectives
  • Complex back-and-forth conversations

The tests follow all coding guidelines:

  • Properly gated behind RUN_AI_TESTS
  • Uses vitest with appropriate mocking
  • Leverages existing test helpers
  • Clear, descriptive test names

Based on coding guidelines

apps/web/utils/outlook/mail.ts (1)

216-218: Good defensive fix for draft ID stability.

Returning the original replyDraft.id ensures a stable, predictable draft ID is returned to callers, even if the PATCH response omits or changes the ID. The comment clearly explains the rationale.

apps/web/utils/email/types.ts (1)

217-217: LGTM - Clean interface extension.

The addition of isSentMessage to the EmailProvider interface is straightforward and follows the existing pattern of message inspection methods like isReplyInThread.

apps/web/utils/email/microsoft.ts (1)

1171-1174: LGTM - Correct implementation.

The isSentMessage implementation correctly checks for the "SENT" label and safely handles the optional labelIds array. The comment clarifies the dependency on internal label mapping that happens beforehand.

apps/web/app/api/ai/digest/route.ts (1)

226-249: No duplicate getRuleNameByExecutedAction found
This function is only defined here; extracting it to a shared utility isn’t necessary at this time.

apps/web/app/api/google/webhook/process-history-item.ts (3)

4-7: LGTM! Clean refactor to shared processor.

The refactor successfully delegates the main processing logic to a centralized shared processor while maintaining Google-specific label event handling. The import changes appropriately support this architectural shift.


39-43: Proper logger scoping.

Good use of .with() to create a context-specific logger within the function scope, following the coding guidelines.


52-55: Verify LABEL_ADDED event handling.

The LABEL_ADDED event just logs and returns without any processing. Is this intentional, or should learning logic be implemented similar to LABEL_REMOVED?

apps/web/utils/reply-tracker/conversation-status-config.ts (1)

1-93: LGTM! Well-structured configuration module.

This new configuration module provides a clean, type-safe abstraction for conversation status mappings. Key strengths:

  • Proper use of as const for immutability
  • Comprehensive mapping functions with null handling for non-conversation SystemTypes
  • Clear type definitions with explicit readonly arrays
  • Serves as single source of truth for status configuration
apps/web/utils/webhook/process-history-item.ts (3)

67-72: Good concurrency guard.

The early concurrency check prevents duplicate processing of the same message, avoiding race conditions.


93-111: Thorough threadId resolution.

Good defensive programming to re-check for existing rules when the threadId is resolved from the message after not being provided initially.


270-286: Robust error handling.

Good provider-specific error handling that gracefully handles "not found" errors for both Google and Outlook, preventing unnecessary exceptions.

apps/web/utils/actions/rule.ts (1)

834-865: safeCreateRule already supports nested fields and optional labelId for LABEL actions. The CreateRuleWithLabelId type and mapActionFields implementation explicitly handle fields and labelId, so no changes are needed.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

2 issues found across 20 files

Prompt for AI agents (all 2 issues)

Understand the root cause of the following 2 issues and fix them.


<file name="apps/web/utils/actions/rule.ts">

<violation number="1" location="apps/web/utils/actions/rule.ts:834">
When the user already has a rule with the same name as this conversation status, safeCreateRule silently returns that conflicting rule and no new system rule is created or enabled. This leaves the toggle broken for those accounts.</violation>
</file>

<file name="apps/web/utils/ai/actions.ts">

<violation number="1" location="apps/web/utils/ai/actions.ts:154">
Removing the coordinateReplyProcess call from the reply action stops us from updating thread trackers after an automated reply, so rules that only run REPLY will no longer move the thread into the NEEDS_REPLY state. Please restore that call (and emailAccountId) to keep reply tracking intact.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/utils/webhook/process-history-item.ts (1)

9-9: Critical: Missing import causing test failures.

The import @/utils/reply-tracker/handle-outbound is missing and causing all tests to fail. Based on line 152 where handleOutboundMessage is called, this file needs to exist.

Verify the import path and ensure the file exists:

#!/bin/bash
# Check if the file exists and show its location
fd -t f "handle-outbound" apps/web/utils/reply-tracker/

# If not found, search for handleOutboundMessage function
rg -n "export.*handleOutboundMessage" apps/web/
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 339c03c and 78a3ed3.

📒 Files selected for processing (7)
  • apps/web/app/api/google/webhook/block-unsubscribed-emails.ts (0 hunks)
  • apps/web/app/api/google/webhook/process-history-item.ts (3 hunks)
  • apps/web/app/api/outlook/webhook/block-unsubscribed-emails.ts (0 hunks)
  • apps/web/utils/email/google.ts (2 hunks)
  • apps/web/utils/email/microsoft.ts (2 hunks)
  • apps/web/utils/email/types.ts (2 hunks)
  • apps/web/utils/webhook/process-history-item.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/web/app/api/outlook/webhook/block-unsubscribed-emails.ts
  • apps/web/app/api/google/webhook/block-unsubscribed-emails.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/email/types.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/email/types.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/email/types.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/google/webhook/process-history-item.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/google/webhook/process-history-item.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/email/google.ts
🧬 Code graph analysis (5)
apps/web/utils/email/types.ts (1)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/app/api/google/webhook/process-history-item.ts (2)
apps/web/app/api/google/webhook/types.ts (3)
  • ProcessHistoryOptions (15-30)
  • HistoryEventType (6-10)
  • HistoryEventType (12-13)
apps/web/app/api/google/webhook/process-label-removed-event.ts (1)
  • handleLabelRemovedEvent (23-107)
apps/web/utils/webhook/process-history-item.ts (10)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/app/api/google/webhook/process-history-item.ts (1)
  • processHistoryItem (9-63)
apps/web/app/api/outlook/webhook/process-history-item.ts (1)
  • processHistoryItem (8-34)
apps/web/utils/assistant/process-assistant-email.ts (1)
  • processAssistantEmail (20-34)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • runColdEmailBlocker (162-188)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/digest/index.ts (1)
  • enqueueDigestItem (11-48)
apps/web/utils/categorize/senders/categorize.ts (1)
  • categorizeSender (17-57)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • runRules (43-97)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/email/google.ts (2)
apps/web/utils/gmail/label.ts (2)
  • labelMessage (133-149)
  • GmailLabel (24-38)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
🪛 GitHub Actions: Run Tests
apps/web/utils/webhook/process-history-item.ts

[error] 9-9: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'. Does the file exist?

🪛 GitHub Check: test
apps/web/utils/webhook/process-history-item.ts

[failure] 9-9: app/api/sso/signin/route.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: app/api/google/webhook/process-history-item.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: utils/outlook/subscription-manager.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: utils/middleware.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: utils/api-auth.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: tests/outlook-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: tests/gmail-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33


[failure] 9-9: tests/ai-process-user-request.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33

🔇 Additional comments (9)
apps/web/utils/email/microsoft.ts (1)

1176-1179: LGTM!

The isSentMessage implementation correctly checks for the "SENT" label. The comment clarifies that internal mapping ensures this works as expected for Outlook.

apps/web/utils/email/types.ts (1)

138-138: LGTM!

The new method signatures blockUnsubscribedEmail and isSentMessage are properly added to the EmailProvider interface, extending the public API surface for both Gmail and Outlook providers.

Also applies to: 218-218

apps/web/utils/email/google.ts (2)

363-373: LGTM!

The blockUnsubscribedEmail implementation correctly:

  1. Creates/retrieves the unsubscribed label
  2. Adds the unsubscribed label to the message
  3. Removes the INBOX label to archive the message

Based on learnings: Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')


881-883: LGTM!

The isSentMessage implementation correctly checks for Gmail's SENT label, aligning with the corresponding Outlook provider implementation.

apps/web/utils/webhook/process-history-item.ts (4)

35-62: LGTM!

The function signature and concurrency guard are well-implemented:

  • Uses markMessageAsProcessing to prevent duplicate processing
  • Early returns if message is already being processed
  • Proper logging for skipped messages

67-107: LGTM!

The message retrieval and existing rule check logic is correctly implemented:

  • Properly handles cases where threadId may not be initially provided
  • Re-checks with the actual threadId from the message if needed
  • Early return to avoid duplicate rule execution

238-254: LGTM!

The error handling correctly identifies and gracefully handles provider-specific "not found" errors:

  • Google: "Requested entity was not found."
  • Outlook: "ItemNotFound" or "ResourceNotFound"
  • Re-throws other errors for proper error propagation

184-192: Email construction verified: getEmailForLLM includes replyTo, cc, and attachments, satisfying runColdEmailBlocker’s EmailForLLM requirements.

apps/web/app/api/google/webhook/process-history-item.ts (1)

4-7: LGTM! Clean refactor to shared processor.

The refactoring successfully centralizes processing logic:

  • Imports and delegates to processHistoryItemShared for the main flow
  • Maintains Google-specific label event handling (LABEL_REMOVED, LABEL_ADDED)
  • Uses scoped logger with contextual fields (email, messageId, threadId)
  • Creates provider instance for label event handling and shared processing

This aligns with the broader PR architecture of centralizing webhook processing.

Also applies to: 29-62

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.

Actionable comments posted: 6

♻️ Duplicate comments (1)
apps/web/utils/actions/rule.ts (1)

834-878: Address the duplicate rule name issue.

As noted in the past review comment, when a user already has a rule with the same name as the conversation status (e.g., a user-created rule named "To Reply"), safeCreateRule with shouldCreateIfDuplicate: false will return that existing rule instead of creating a new system rule. This creates several problems:

  1. The returned rule may not have the correct systemType (it won't be linked to the conversation status system)
  2. The rule may not have the required TRACK_THREAD action
  3. The toggle will appear enabled but won't work correctly for conversation tracking

Based on the past review suggestion, apply this diff to force creation even when a duplicate name exists:

         await safeCreateRule({
           result: {
             name: statusConfig.name,
             condition: {
               aiInstructions:
                 "Personal conversations with real people. Excludes: automated notifications and bulk emails.",
               conditionalOperator: null,
               static: null,
             },
             actions: [
               {
                 type: ActionType.LABEL,
                 labelId: labelInfo.labelId,
                 fields: {
                   label: labelInfo.label,
                   to: null,
                   subject: null,
                   content: null,
                   cc: null,
                   bcc: null,
                   webhookUrl: null,
                   folderName: null,
                 },
               },
               {
                 type: ActionType.TRACK_THREAD,
                 fields: {
                   label: null,
                   to: null,
                   subject: null,
                   content: null,
                   cc: null,
                   bcc: null,
                   webhookUrl: null,
                   folderName: null,
                 },
               },
             ],
           },
           emailAccountId,
           systemType,
           triggerType: "manual_creation",
-          shouldCreateIfDuplicate: false,
+          shouldCreateIfDuplicate: true,
           provider,
         });
🧹 Nitpick comments (4)
apps/web/utils/reply-tracker/draft-tracking.ts (1)

7-232: LGTM! Dependency-injected logger improves testability.

The shift from module-scoped logger creation to dependency-injected logger parameters in both trackSentDraftStatus and cleanupThreadAIDrafts is a recommended refactor. This approach:

  • Enables easier testing by allowing mock loggers to be injected
  • Maintains consistent logging context from calling code
  • Aligns with broader patterns in the codebase (e.g., process-history-item flows)

All logging calls have been updated consistently to use the injected logger parameter.

apps/web/utils/reply-tracker/conversation-status-config.ts (1)

52-65: Consider defining the status types array at module level.

The CONVERSATION_STATUS_TYPES array is recreated on every call to isConversationStatusType. For better performance, define it once at the module level.

+const CONVERSATION_STATUS_TYPES: SystemType[] = [
+  SystemType.TO_REPLY,
+  SystemType.FYI,
+  SystemType.AWAITING_REPLY,
+  SystemType.ACTIONED,
+];
+
 export function isConversationStatusType(
   systemType: SystemType | null | undefined,
 ): boolean {
   if (!systemType) return false;

-  const CONVERSATION_STATUS_TYPES: string[] = [
-    SystemType.TO_REPLY,
-    SystemType.FYI,
-    SystemType.AWAITING_REPLY,
-    SystemType.ACTIONED,
-  ];
-
   return CONVERSATION_STATUS_TYPES.includes(systemType);
 }

Alternatively, you could derive it from CONVERSATION_STATUSES:

const CONVERSATION_STATUS_TYPES = CONVERSATION_STATUSES.map(s => s.systemType);
apps/web/utils/reply-tracker/handle-outbound.ts (1)

26-46: Consider simplifying error handling pattern.

The current approach uses .catch() on each promise before passing to Promise.allSettled, which means Promise.allSettled will always see both promises as fulfilled (the catch blocks prevent rejection). Since the results aren't used, this works fine but is slightly redundant.

You can simplify by removing the individual .catch() calls and handling errors in the settled results:

-  await Promise.allSettled([
+  const results = await Promise.allSettled([
     trackSentDraftStatus({
       emailAccountId: emailAccount.id,
       message,
       provider,
       logger,
-    }).catch((error) => {
-      logger.error("Error tracking sent draft status", {
-        error: formatError(error),
-      });
-    }),
+    }),
     handleOutboundReply({
       emailAccount,
       message,
       provider,
-    }).catch((error) => {
-      logger.error("Error handling outbound reply", {
-        error: formatError(error),
-      });
-    }),
+    }),
   ]);
+
+  results.forEach((result, index) => {
+    if (result.status === "rejected") {
+      const operation = index === 0 ? "tracking sent draft status" : "handling outbound reply";
+      logger.error(`Error ${operation}`, {
+        error: formatError(result.reason),
+      });
+    }
+  });

Alternatively, keep the current pattern if you prefer the readability of inline error handling.

apps/web/utils/actions/rule.ts (1)

815-820: Verify and restore rule configuration on re-enable
When toggling a rule back on we only set enabled: true (apps/web/utils/actions/rule.ts:815-820). If its actions or labels were modified, re-enabling won’t restore the expected behavior. Add logic to verify (and upsert) required actions (e.g. TRACK_THREAD) and other settings when enable: true.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78a3ed3 and 695014d.

📒 Files selected for processing (17)
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (3 hunks)
  • apps/web/app/api/google/webhook/process-history-item.test.ts (8 hunks)
  • apps/web/utils/actions/rule.ts (4 hunks)
  • apps/web/utils/ai/choose-rule/run-rules.ts (5 hunks)
  • apps/web/utils/auth.ts (1 hunks)
  • apps/web/utils/outlook/label.ts (1 hunks)
  • apps/web/utils/reply-tracker/conversation-status-config.ts (1 hunks)
  • apps/web/utils/reply-tracker/draft-tracking.ts (8 hunks)
  • apps/web/utils/reply-tracker/handle-conversation-status.ts (1 hunks)
  • apps/web/utils/reply-tracker/handle-outbound.ts (1 hunks)
  • apps/web/utils/reply-tracker/inbound.ts (3 hunks)
  • apps/web/utils/reply-tracker/label-helpers.test.ts (1 hunks)
  • apps/web/utils/reply-tracker/label-helpers.ts (1 hunks)
  • apps/web/utils/reply-tracker/outbound.ts (3 hunks)
  • apps/web/utils/rule/rule.ts (3 hunks)
  • apps/web/utils/scheduled-actions/executor.test.ts (2 hunks)
  • apps/web/utils/webhook/process-history-item.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (25)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
**/*.test.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/utils/rule/rule.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/utils/rule/rule.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/reply-tracker/handle-outbound.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/conversation-status-config.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
  • apps/web/utils/rule/rule.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/reply-tracker/label-helpers.test.ts
  • apps/web/utils/scheduled-actions/executor.test.ts
  • apps/web/app/api/google/webhook/process-history-item.test.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/api/google/webhook/process-history-item.test.ts
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
apps/web/app/(app)/*/**

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
apps/web/app/(app)/*/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
apps/web/app/(app)/*/**/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/google/webhook/process-history-item.test.ts
🧠 Learnings (4)
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.

Applied to files:

  • apps/web/utils/reply-tracker/inbound.ts
  • apps/web/utils/rule/rule.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : In tests, mock the `server-only` module with `vi.mock("server-only", () => ({}));`

Applied to files:

  • apps/web/app/api/google/webhook/process-history-item.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : When testing code that uses Prisma, mock it with `vi.mock("@/utils/prisma")` and use the mock from `@/utils/__mocks__/prisma`

Applied to files:

  • apps/web/app/api/google/webhook/process-history-item.test.ts
📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.053Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests

Applied to files:

  • apps/web/app/api/google/webhook/process-history-item.test.ts
🧬 Code graph analysis (16)
apps/web/utils/reply-tracker/label-helpers.test.ts (1)
apps/web/utils/reply-tracker/label-helpers.ts (2)
  • applyThreadStatusLabel (108-171)
  • removeAllThreadStatusLabels (177-203)
apps/web/utils/reply-tracker/handle-outbound.ts (6)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/reply-tracker/draft-tracking.ts (2)
  • trackSentDraftStatus (12-112)
  • cleanupThreadAIDrafts (119-233)
apps/web/utils/error.ts (1)
  • formatError (51-61)
apps/web/utils/reply-tracker/outbound.ts (1)
  • handleOutboundReply (12-91)
apps/web/utils/ai/choose-rule/run-rules.ts (4)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
  • findMatchingRule (202-226)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • determineConversationStatus (21-100)
apps/web/utils/types.ts (1)
  • RuleWithActionsAndCategories (16-18)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (52-65)
apps/web/utils/actions/rule.ts (6)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (62-113)
apps/web/utils/actions/rule.validation.ts (1)
  • toggleConversationStatusBody (210-218)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • CONVERSATION_STATUSES (17-50)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/rule/rule.ts (1)
  • safeCreateRule (43-109)
apps/web/utils/auth.ts (2)
apps/web/utils/error.ts (1)
  • captureException (63-75)
apps/web/utils/dub.ts (1)
  • trackDubSignUp (13-41)
apps/web/utils/webhook/process-history-item.ts (12)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/utils/types.ts (1)
  • RuleWithActionsAndCategories (16-18)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/assistant/process-assistant-email.ts (1)
  • processAssistantEmail (20-34)
apps/web/utils/reply-tracker/handle-outbound.ts (1)
  • handleOutboundMessage (9-60)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • runColdEmailBlocker (162-188)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/digest/index.ts (1)
  • enqueueDigestItem (11-48)
apps/web/utils/categorize/senders/categorize.ts (1)
  • categorizeSender (17-57)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • runRules (47-129)
apps/web/utils/reply-tracker/outbound.ts (5)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-98)
apps/web/utils/reply-tracker/label-helpers.ts (1)
  • applyThreadStatusLabel (108-171)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • updateThreadTrackers (102-156)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (4)
  • NEEDS_REPLY_LABEL_NAME (1-1)
  • FYI_LABEL_NAME (3-3)
  • AWAITING_REPLY_LABEL_NAME (2-2)
  • ACTIONED_LABEL_NAME (4-4)
apps/web/utils/outlook/label.ts (1)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/reply-tracker/inbound.ts (7)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-98)
apps/web/utils/reply-tracker/label-helpers.ts (1)
  • applyThreadStatusLabel (108-171)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • updateThreadTrackers (102-156)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (3)
  • isConversationStatusType (52-65)
  • CONVERSATION_STATUSES (17-50)
  • ThreadStatus (9-9)
apps/web/utils/actions/rule.ts (1)
  • toggleConversationStatusAction (788-890)
apps/web/utils/reply-tracker/label-helpers.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (2)
  • CONVERSATION_STATUSES (17-50)
  • ConversationStatusLabelType (11-15)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/utils/reply-tracker/draft-tracking.ts (3)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/reply-tracker/handle-conversation-status.ts (6)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/types.ts (2)
  • RuleWithActionsAndCategories (16-18)
  • ParsedMessage (55-70)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-98)
apps/web/app/api/google/webhook/process-history-item.test.ts (3)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/webhook/process-history-item.ts (1)
  • processHistoryItem (35-255)
apps/web/app/api/google/webhook/process-history-item.ts (1)
  • processHistoryItem (9-63)
apps/web/utils/rule/rule.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (52-65)
apps/web/utils/email/provider-types.ts (1)
  • isMicrosoftProvider (5-7)
🔇 Additional comments (7)
apps/web/utils/auth.ts (4)

179-179: Silent failures in parallel operations.

Since each promise has a .catch handler that doesn't rethrow, Promise.all will always resolve successfully even if all operations fail. This means the sign-in flow continues normally regardless of whether Loops, Resend, or Dub integrations succeed. While this may be intentional for non-critical operations, consider whether the caller should have visibility into these failures (e.g., returning success/failure counts).


148-179: Simplified error handling approach.

The migration from Promise.allSettled to individual .catch handlers simplifies the code and treats these operations as best-effort (failing silently without blocking user sign-in). This aligns with the PR's goal of modularizing error handling.


163-169: Align Resend duplicate handling with Loops. Add a 409-conflict check in the .catch and skip logging/capturing when the contact already exists, mirroring the createLoopsContact block.


152-153: Avoid brittle error.message matching for 409
createLoopsContact is imported from @inboxzero/loops, so its error messages aren’t under our control. Rather than error.message.includes("409"), verify whether the thrown error exposes a status code (e.g. error.statusCode === 409 or error.response?.status === 409) or a dedicated error class, and use that for duplicate-resource detection.

apps/web/utils/scheduled-actions/executor.test.ts (1)

73-75: LGTM! Test mocks updated to match new ExecutedAction schema.

The addition of labelId, folderName, and folderId fields (all set to null) correctly aligns the test mocks with the expanded ExecutedAction data shape introduced in this PR.

Also applies to: 162-164

apps/web/utils/rule/rule.ts (1)

176-202: LGTM! Automatic TRACK_THREAD action for conversation status rules.

The logic correctly ensures that conversation-status system rules (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED) automatically include a TRACK_THREAD action if one isn't already present. This prevents users from creating conversation-status rules that don't actually track threads.

The conditional spreading for Microsoft providers (line 199) and proper null initialization for all fields are appropriate.

apps/web/utils/reply-tracker/label-helpers.test.ts (1)

1-289: LGTM! Comprehensive test coverage for label helpers.

The test suite thoroughly covers:

  • All four conversation status types (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED)
  • Correct label application via labelMessage
  • Removal of other thread status labels via removeThreadLabel
  • Verification that the target label is not removed
  • Graceful error handling for both label application and removal failures
  • Complete cleanup of all thread status labels via removeAllThreadStatusLabels

The mock setup is correct with proper cleanup in beforeEach, and the test assertions are clear and comprehensive.

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/utils/ai/choose-rule/run-rules.ts (1)

62-108: Defer sender-pattern analysis until after resolving the meta rule

Calling analyzeSenderPatternIfAiMatch before we swap the meta rule for the concrete conversation-status rule means the guard in shouldAnalyzeSenderPattern now sees SystemType === null. As soon as the meta rule matches a To Reply thread, we no longer skip analysis—the safeguard from the original code regresses and we start running sender-pattern learning for TO_REPLY conversations. Please move the analysis call to run against ruleToExecute (after the meta rule has been resolved) so the system type check continues to work as intended.

Here’s one way to adjust it:

-  analyzeSenderPatternIfAiMatch({
-    isTest,
-    result,
-    message,
-    emailAccountId: emailAccount.id,
-  });
-
   if (result.rule) {
     let ruleToExecute = result.rule;
     let reasonToUse = result.reason;
@@
+    analyzeSenderPatternIfAiMatch({
+      isTest,
+      result: {
+        rule: ruleToExecute,
+        matchReasons: result.matchReasons,
+      },
+      message,
+      emailAccountId: emailAccount.id,
+    });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 695014d and dfe433b.

📒 Files selected for processing (6)
  • apps/web/utils/actions/rule.ts (4 hunks)
  • apps/web/utils/ai/choose-rule/run-rules.ts (5 hunks)
  • apps/web/utils/email/microsoft.ts (2 hunks)
  • apps/web/utils/outlook/label.ts (1 hunks)
  • apps/web/utils/reply-tracker/label-helpers.ts (1 hunks)
  • apps/web/utils/rule/rule.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/email/microsoft.ts
🧰 Additional context used
📓 Path-based instructions (12)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/reply-tracker/label-helpers.ts
  • apps/web/utils/actions/rule.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.ts
🧠 Learnings (3)
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.

Applied to files:

  • apps/web/utils/rule/rule.ts
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.

Applied to files:

  • apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation

Applied to files:

  • apps/web/utils/actions/rule.ts
🧬 Code graph analysis (4)
apps/web/utils/ai/choose-rule/run-rules.ts (4)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
  • findMatchingRule (202-226)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • determineConversationStatus (21-100)
apps/web/utils/types.ts (1)
  • RuleWithActionsAndCategories (16-18)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (52-65)
apps/web/utils/rule/rule.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (52-65)
apps/web/utils/email/provider-types.ts (1)
  • isMicrosoftProvider (5-7)
apps/web/utils/reply-tracker/label-helpers.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/reply-tracker/conversation-status-config.ts (2)
  • CONVERSATION_STATUSES (17-50)
  • ConversationStatusLabelType (11-15)
apps/web/utils/email/types.ts (1)
  • EmailProvider (43-226)
apps/web/utils/actions/rule.ts (7)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (62-113)
apps/web/utils/actions/rule.validation.ts (1)
  • toggleConversationStatusBody (210-218)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • CONVERSATION_STATUSES (17-50)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/rule/rule.ts (1)
  • safeCreateRule (43-132)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (5)
apps/web/utils/reply-tracker/label-helpers.ts (5)

1-10: LGTM! Clean imports and logger setup.

The imports are well-organized and the logger is correctly initialized at the top level using createScopedLogger as per coding guidelines.


12-22: LGTM! Well-structured constants and type mappings.

The derived constants and helper function provide a clean abstraction layer. The statusToLabelType function appropriately throws for unknown status types, enforcing type safety at runtime.


178-201: LGTM! Past review concern is properly addressed.

This implementation correctly addresses the previous review comment by calling getAllThreadStatusLabels (line 186) instead of the create variant. The helper function (defined at lines 207-276) only fetches existing labels by name without creating new ones (see line 257), ensuring no unexpected label creation during status clearing.

Based on past review comments that flagged unwanted label creation.


207-276: LGTM! Fetch-only implementation is correct.

This helper correctly implements fetch-only behavior (no label creation). The key difference from getOrCreateAllThreadStatusLabels is at line 257 where it only updates dbLabelIds[type] if the label already exists in the provider—no creation occurs.

This design properly supports the use case in removeAllThreadStatusLabels where we only want to clear existing labels without side effects.

Note: Similar to getOrCreateAllThreadStatusLabels, this function returns partial results on error (line 274) and has the same DB update failure considerations at lines 264-269. See the earlier comment for details.


64-100: Consider DB update failures leading to orphaned provider labels
If prisma.emailAccount.update fails after creating labels via provider.createLabel, remote labels persist but the DB isn’t updated, and the catch returns partial IDs—subsequent calls will retry creation (risking duplicates or errors). Log or propagate the update error, or roll back provider changes to maintain consistency.

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.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/api/google/webhook/process-label-removed-event.test.ts (1)

94-94: Remove debug console.log statement.

This console.log statement violates the coding guideline "Don't use console." Debug statements should be removed before merging.

As per coding guidelines.

Apply this diff:

-      console.log("Test data:", JSON.stringify(historyItem.item, null, 2));
-
       try {
🧹 Nitpick comments (2)
apps/web/app/api/google/webhook/process-label-removed-event.test.ts (2)

35-35: Consider extracting the hard-coded label name to a constant.

The hard-coded "Cold Email" string makes the test more brittle if the label name changes. Consider defining a constant at the top of the test file or importing it from a shared location.

Example:

+const COLD_EMAIL_LABEL_NAME = "Cold Email";
+
 vi.mock("@/utils/gmail/label", () => ({
   GmailLabel: {
     // ...
   },
   getLabelById: vi.fn().mockImplementation(({ id }: { id: string }) => {
     const labelMap: Record<string, { name: string }> = {
-      "label-1": { name: "Cold Email" },
+      "label-1": { name: COLD_EMAIL_LABEL_NAME },
       // ...
     };
     return Promise.resolve(labelMap[id] || { name: "Unknown Label" });
   }),
 }));

76-76: Consider using the same constant here as suggested for line 35.

If you extract the label name to a constant as suggested above, apply the same change here for consistency.

Example:

     getLabels: vi.fn().mockResolvedValue([
-      { id: "label-1", name: "Cold Email", type: "user" },
+      { id: "label-1", name: COLD_EMAIL_LABEL_NAME, type: "user" },
       { id: "label-2", name: "Newsletter", type: "user" },
       // ...
     ]),
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a143fc and 51efc41.

📒 Files selected for processing (2)
  • apps/web/__tests__/ai-choose-rule.test.ts (2 hunks)
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/*.test.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
apps/web/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Place LLM-specific tests under apps/web/tests/

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
**/__tests__/**

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Place AI tests in the __tests__ directory and exclude them from the default test run (they use a real LLM)

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
apps/web/__tests__/**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/llm-test.mdc)

apps/web/__tests__/**/*.test.ts: Place all LLM-related tests under apps/web/tests/
Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"
Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests
Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")
Call vi.clearAllMocks() in a beforeEach for LLM tests
Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks
Create helper functions for common test data (e.g., getUser, getTestData) to reduce duplication
Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats
Use console.debug to log generated LLM content for inspection (e.g., console.debug("Generated content:\n", result.content))
Do not mock the actual LLM call in these tests; exercise real LLM integrations
Test both AI and non-AI paths, including cases where no AI processing is required
Prefer existing helpers from @/tests/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/google/webhook/process-label-removed-event.test.ts
🧠 Learnings (5)
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use provided helpers for mocks: import `{ getEmail, getEmailAccount, getRule }` from `@/__tests__/helpers`

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest (`vitest`) as the testing framework

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
🧬 Code graph analysis (1)
apps/web/__tests__/ai-choose-rule.test.ts (1)
apps/web/__tests__/helpers.ts (1)
  • getRule (44-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (2)
apps/web/__tests__/ai-choose-rule.test.ts (2)

72-72: LGTM! Schema alignment for label references.

The addition of labelId: null aligns the mock action with the updated schema that now supports both string labels and label ID references, consistent with the PR's migration to dynamic rule/label configurations.


107-107: Confirm literal captures original intent
I couldn't locate defaultReplyTrackerInstructions in the codebase—ensure "Match emails requiring a response" fully encompasses the original instructions and matching criteria.

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.

Actionable comments posted: 5

♻️ Duplicate comments (6)
apps/web/app/api/ai/digest/route.ts (2)

47-54: Exception bypasses null check; account-scope the lookup.

getRuleNameByExecutedAction throws at line 245 if the executed action is not found, so the !ruleName guard never runs—this will cause 500 errors. Additionally, returning early when actionId is falsy may skip valid digest items without an action ID. Per the past review, the lookup must also be scoped by emailAccountId to prevent cross-account leakage.

Apply this diff:

-      const ruleName = actionId
-        ? await getRuleNameByExecutedAction(actionId)
-        : null;
-
-      if (!ruleName) {
-        logger.warn("Rule name not found for executed action", { actionId });
-        return new NextResponse("OK", { status: 200 });
-      }
+      const ruleName = actionId
+        ? await getRuleNameByExecutedAction(actionId, emailAccountId)
+        : null;
+
+      if (!ruleName) {
+        logger.warn("Rule name not found for executed action", { actionId });
+        return new NextResponse("OK", { status: 200 });
+      }

And update getRuleNameByExecutedAction below to accept and use emailAccountId.


226-249: Missing account scoping enables IDOR; return undefined instead of throwing.

This function has two critical issues:

  1. IDOR vulnerability: The findUnique query lacks an emailAccountId filter, allowing any caller with a valid actionId to retrieve rule names from other accounts.
  2. Inconsistent error handling: The function signature promises Promise<string | undefined>, but line 245 throws instead of returning undefined.

As per coding guidelines, all Prisma queries in API routes must include ownership filters to prevent unauthorized data access.

Apply this diff to scope by account and return undefined on missing action:

 async function getRuleNameByExecutedAction(
   actionId: string,
+  emailAccountId: string,
 ): Promise<string | undefined> {
-  const executedAction = await prisma.executedAction.findUnique({
-    where: { id: actionId },
+  const executedAction = await prisma.executedAction.findFirst({
+    where: {
+      id: actionId,
+      executedRule: {
+        rule: {
+          emailAccountId,
+        },
+      },
+    },
     select: {
       executedRule: {
         select: {
           rule: {
             select: {
               name: true,
             },
           },
         },
       },
     },
   });
 
-  if (!executedAction) {
-    throw new Error("Executed action not found");
-  }
-
-  return executedAction.executedRule?.rule?.name;
+  return executedAction?.executedRule?.rule?.name;
 }
apps/web/utils/ai/choose-rule/run-rules.ts (1)

86-113: Critical: Sender pattern analysis still skipped for conversation rules

The analyzeSenderPatternIfAiMatch call is placed in the else branch (lines 107-112), meaning it only runs for non-meta rules. Conversation rules (FYI, ACTIONED, AWAITING_REPLY) are resolved through the meta-rule path, so they never trigger sender learning—exactly the regression flagged in the previous review. Move the analysis call to after the entire if/else block so it uses the final ruleToExecute.

Apply this diff to restore sender learning for conversation matches:

     // Check if this is the conversation tracking meta-rule
     if (result.rule.id === CONVERSATION_TRACKING_META_RULE_ID) {
       // Determine which specific sub-rule applies
       const { specificRule, reason: statusReason } =
         await determineConversationStatus({
           conversationRules,
           message,
           emailAccount,
           provider,
           modelType,
         });

       if (!specificRule) {
         return {
           rule: null,
           reason: statusReason || "No enabled conversation status rule found",
         };
       }

       ruleToExecute = specificRule;
       reasonToUse = statusReason;
-    } else {
-      analyzeSenderPatternIfAiMatch({
-        isTest,
-        result,
-        message,
-        emailAccountId: emailAccount.id,
-      });
     }
+
+    analyzeSenderPatternIfAiMatch({
+      isTest,
+      result: {
+        rule: ruleToExecute,
+        matchReasons: result.matchReasons,
+      },
+      message,
+      emailAccountId: emailAccount.id,
+    });

     return await executeMatchedRule(
       ruleToExecute,
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)

69-72: Add error handling around AI call.

External AI calls can throw; add try/catch to avoid unhandled rejections and log failures. As per coding guidelines and learnings, use proper error handling with try/catch and implement fallbacks for AI failures.

Apply this diff to add error handling:

+  let status: SystemType;
+  let rationale: string;
+  try {
-  const { status, rationale } = await aiDetermineThreadStatus({
+    const aiResult = await aiDetermineThreadStatus({
       emailAccount,
       threadMessages: threadMessagesForLLM,
     });
+    status = aiResult.status;
+    rationale = aiResult.rationale;
+  } catch (error) {
+    logger.error("AI thread status determination failed", { error });
+    return {
+      specificRule: null,
+      reason: "AI analysis failed",
+    };
+  }
apps/web/utils/reply-tracker/outbound.ts (2)

70-73: Add error handling around AI call.

External AI calls can throw; add try/catch to avoid unhandled rejections and log failures. As per coding guidelines and learnings, use proper error handling with try/catch and implement fallbacks for AI failures.

Apply this diff to add error handling:

+  let aiResult;
+  try {
-  const aiResult = await aiDetermineThreadStatus({
+    aiResult = await aiDetermineThreadStatus({
       emailAccount,
       threadMessages: threadMessagesForLLM,
     });
+  } catch (error) {
+    logger.error("AI thread status determination failed", { error });
+    return;
+  }

85-91: Add error handling around database update.

Database operations can fail; add try/catch to handle potential errors. As per coding guidelines, use proper error handling with try/catch blocks.

Apply this diff to add error handling:

+  try {
     await updateThreadTrackers({
       emailAccountId: emailAccount.id,
       threadId: message.threadId,
       messageId: message.id,
       sentAt: internalDateToDate(message.internalDate),
       status: aiResult.status,
     });
+  } catch (error) {
+    logger.error("Failed to update thread trackers", { error });
+  }
🧹 Nitpick comments (3)
apps/web/app/api/google/webhook/process-history.ts (2)

66-78: Use consistent variable for lastSyncedHistoryId.

Lines 69 and 75 reference emailAccount?.lastSyncedHistoryId, but the rest of the function uses validatedEmailAccount. While technically safe (both reference the same object), this inconsistency could confuse future maintainers.

Apply this diff for consistency:

     const startHistoryId =
       options?.startHistoryId ||
       Math.max(
-        Number.parseInt(emailAccount?.lastSyncedHistoryId || "0"),
+        Number.parseInt(validatedEmailAccount.lastSyncedHistoryId || "0"),
         historyId - 500, // avoid going too far back
       ).toString();

     logger.info("Listing history", {
       startHistoryId,
-      lastSyncedHistoryId: emailAccount?.lastSyncedHistoryId,
+      lastSyncedHistoryId: validatedEmailAccount.lastSyncedHistoryId,
       gmailHistoryId: startHistoryId,
       email,
     });

95-117: Consider simplifying emailAccount reconstruction.

Lines 102-116 manually reconstruct the emailAccount object by selecting fields from validatedEmailAccount. If the ProcessHistoryOptions.emailAccount type is compatible with the validated account structure, you could pass validatedEmailAccount directly to reduce code duplication.

Verify type compatibility and consider:

await processHistory({
  history: history.history,
  gmail,
  accessToken: accountAccessToken,
  hasAutomationRules,
  hasAiAccess: userHasAiAccess,
  rules: validatedEmailAccount.rules,
  emailAccount: validatedEmailAccount, // if types match
});
apps/web/app/api/outlook/webhook/process-history.ts (1)

49-63: Consider simplifying emailAccount reconstruction.

Similar to the Google webhook, lines 49-63 manually reconstruct the emailAccount object. If the ProcessHistoryOptions.emailAccount type matches the validated structure, passing validatedEmailAccount directly would reduce duplication.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51efc41 and f8079fe.

📒 Files selected for processing (8)
  • apps/web/app/api/ai/digest/route.ts (2 hunks)
  • apps/web/app/api/google/webhook/process-history.ts (4 hunks)
  • apps/web/app/api/outlook/webhook/process-history.ts (2 hunks)
  • apps/web/utils/ai/choose-rule/run-rules.ts (5 hunks)
  • apps/web/utils/reply-tracker/handle-conversation-status.ts (1 hunks)
  • apps/web/utils/reply-tracker/outbound.ts (4 hunks)
  • apps/web/utils/webhook/validate-webhook-account.test.ts (1 hunks)
  • apps/web/utils/webhook/validate-webhook-account.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (17)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/app/api/google/webhook/process-history.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/app/api/google/webhook/process-history.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/ai/digest/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/ai/digest/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/outlook/webhook/process-history.ts
  • apps/web/app/api/google/webhook/process-history.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/run-rules.ts
**/*.test.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/webhook/validate-webhook-account.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/webhook/validate-webhook-account.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/webhook/validate-webhook-account.test.ts
🧠 Learnings (4)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/utils/reply-tracker/outbound.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/reply-tracker/outbound.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer existing helpers from @/__tests__/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers

Applied to files:

  • apps/web/utils/webhook/validate-webhook-account.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats

Applied to files:

  • apps/web/utils/webhook/validate-webhook-account.test.ts
🧬 Code graph analysis (7)
apps/web/utils/reply-tracker/outbound.ts (6)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-97)
apps/web/utils/reply-tracker/label-helpers.ts (1)
  • applyThreadStatusLabel (17-94)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • updateThreadTrackers (105-159)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • CONVERSATION_STATUS_TYPES (3-8)
apps/web/utils/reply-tracker/handle-conversation-status.ts (7)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/types.ts (2)
  • RuleWithActionsAndCategories (16-18)
  • ParsedMessage (55-70)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/get-email-from-message.ts (1)
  • getEmailForLLM (6-25)
apps/web/utils/ai/reply/determine-thread-status.ts (1)
  • aiDetermineThreadStatus (10-97)
apps/web/app/api/outlook/webhook/process-history.ts (5)
apps/web/utils/webhook/validate-webhook-account.ts (2)
  • getWebhookEmailAccount (7-59)
  • validateWebhookAccount (75-155)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/webhook/process-history-item.ts (1)
  • processHistoryItem (26-216)
apps/web/app/api/outlook/webhook/process-history-item.ts (1)
  • processHistoryItem (18-44)
apps/web/utils/error.ts (1)
  • captureException (63-75)
apps/web/utils/webhook/validate-webhook-account.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/premium/index.ts (2)
  • isPremium (14-22)
  • hasAiAccess (87-101)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/app/api/google/webhook/process-history.ts (2)
apps/web/utils/webhook/validate-webhook-account.ts (2)
  • getWebhookEmailAccount (7-59)
  • validateWebhookAccount (75-155)
apps/web/utils/gmail/client.ts (1)
  • getGmailClientWithRefresh (49-103)
apps/web/utils/ai/choose-rule/run-rules.ts (8)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
  • findMatchingRule (207-259)
apps/web/utils/reply-tracker/handle-conversation-status.ts (2)
  • determineConversationStatus (22-103)
  • updateThreadTrackers (105-159)
apps/web/utils/types.ts (1)
  • RuleWithActionsAndCategories (16-18)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (16-22)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • saveColdEmail (165-191)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/scheduled-actions/scheduler.ts (2)
  • cancelScheduledActions (167-247)
  • scheduleDelayedActions (116-165)
apps/web/utils/ai/choose-rule/execute.ts (1)
  • executeAct (14-73)
apps/web/utils/webhook/validate-webhook-account.test.ts (3)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/webhook/validate-webhook-account.ts (2)
  • ValidatedWebhookAccountData (61-63)
  • validateWebhookAccount (75-155)
apps/web/utils/premium/index.ts (2)
  • isPremium (14-22)
  • hasAiAccess (87-101)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (8)
apps/web/utils/ai/choose-rule/run-rules.ts (4)

32-38: LGTM!

The new imports are appropriate for the conversation tracking and cold email features.


50-50: LGTM!

The constant is properly defined for identifying the conversation tracking meta-rule.


136-168: LGTM!

The meta-rule preparation correctly separates conversation rules and creates a meta-rule with systemType: null (line 159), which properly addresses the previous review concern about systemType inheritance.


197-270: LGTM!

The execution flow is well-structured:

  • Immediate actions are saved and executed right away
  • Cold email metadata is correctly persisted for COLD_EMAIL rules
  • Thread trackers are updated for conversation status types
  • Delayed actions are properly scheduled after canceling any existing ones
  • Rules with no actions are marked as APPLIED immediately
apps/web/utils/webhook/validate-webhook-account.test.ts (1)

1-314: LGTM! Comprehensive test coverage.

The test suite thoroughly covers all validation paths including null accounts, non-premium scenarios, missing AI access, missing automation rules, missing tokens, and success cases. The use of a custom createMockEmailAccount helper is appropriate here since it creates the specific ValidatedWebhookAccountData type which differs from the generic test helpers in @/__tests__/helpers.

apps/web/utils/webhook/validate-webhook-account.ts (1)

7-59: LGTM! Well-structured account query.

The conditional query logic correctly handles both email and subscription ID lookups, and the select clause includes all necessary fields for downstream validation and processing.

apps/web/app/api/google/webhook/process-history.ts (1)

46-52: Token check after validation is redundant but acceptable.

The validation at line 34 already ensures tokens exist (see validateWebhookAccount lines 137-145). However, this check provides explicit type narrowing for TypeScript and adds a helpful debug log at a critical point in the flow.

apps/web/app/api/outlook/webhook/process-history.ts (1)

19-41: LGTM! Correct provider fallback for Outlook.

The validation flow is clean, and the "microsoft" fallback at line 36 correctly handles Outlook accounts when the provider field is null. This approach works around the issue flagged in validateWebhookAccount by creating the provider separately with the correct fallback.

message: ParsedMessage;
emailAccount: EmailAccountWithAI;
provider: EmailProvider;
modelType: ModelType;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused parameter.

The modelType parameter is declared but never used in the function body.

Apply this diff to remove the unused parameter:

   provider: EmailProvider;
-  modelType: ModelType;
 }): Promise<{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modelType: ModelType;
provider: EmailProvider;
}): Promise<{
🤖 Prompt for AI Agents
In apps/web/utils/reply-tracker/handle-conversation-status.ts around line 32,
the function declaration includes a modelType parameter that is never used;
remove modelType from the function signature (and any corresponding
type/interface for that function) and update all call sites to stop passing
modelType so the parameter is no longer required; ensure TypeScript types are
adjusted (remove modelType from the parameter type) and run type checks to
confirm there are no remaining references.

Comment on lines 95 to 98
const provider = await createEmailProvider({
emailAccountId: emailAccount.id,
provider: emailAccount.account?.provider || "google",
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Provider fallback may be incorrect for Outlook webhooks.

The fallback to "google" at line 97 could cause issues for Outlook webhooks when provider is null. The Outlook webhook handler uses "microsoft" as its fallback (see apps/web/app/api/outlook/webhook/process-history.ts lines 35-36), creating an inconsistency. If an Outlook account has a null provider field, this function will attempt to create a Google provider for unwatchEmails, which will fail when createEmailProvider tries to initialize a Gmail client for an Outlook account.

Consider one of these solutions:

  1. Accept a providerType parameter to use the correct fallback
  2. Determine the provider based on the query type (watchEmailsSubscriptionId likely indicates Outlook)
  3. Handle the provider creation error gracefully in the unwatchEmails flow

Apply this diff to accept a provider fallback parameter:

 export async function validateWebhookAccount(
   emailAccount: ValidatedWebhookAccountData | null,
+  providerFallback: string,
   logger: {
     error: (message: string, context?: Record<string, unknown>) => void;
     info: (message: string, context?: Record<string, unknown>) => void;
     trace: (message: string, context?: Record<string, unknown>) => void;
   },
 ): Promise<ValidationResult> {
   if (!emailAccount) {
     logger.error("Account not found");
     return { success: false, response: NextResponse.json({ ok: true }) };
   }

   const premium = isPremium(
     emailAccount.user.premium?.lemonSqueezyRenewsAt || null,
     emailAccount.user.premium?.stripeSubscriptionStatus || null,
   )
     ? emailAccount.user.premium
     : undefined;

   const provider = await createEmailProvider({
     emailAccountId: emailAccount.id,
-    provider: emailAccount.account?.provider || "google",
+    provider: emailAccount.account?.provider || providerFallback,
   });

Then update the callers to pass the appropriate fallback:

// In Google webhook
const validation = await validateWebhookAccount(emailAccount, "google", logger);

// In Outlook webhook
const validation = await validateWebhookAccount(emailAccount, "microsoft", logger);

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.

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/web/utils/email/google.ts (1)

364-377: Fix unsubscribed labeling guard before archiving.

The guard is inverted: unsubscribeLabel?.id is truthy when the label exists, so we always log “not found” yet still proceed even if creation failed. Worse, if getOrCreateInboxZeroLabel ever returns without an id, we still strip INBOX, leaving the message untracked. Please bail out when the label is missing and only archive once we have a concrete label id.

   const unsubscribeLabel =
     await this.getOrCreateInboxZeroLabel("unsubscribed");

-    if (unsubscribeLabel?.id) {
-      logger.warn("Unsubscribe label not found", { messageId });
-    }
-
-    await labelMessage({
+    if (!unsubscribeLabel?.id) {
+      logger.error("Failed to get or create unsubscribed label", { messageId });
+      throw new Error("Cannot block email without unsubscribed label");
+    }
+
+    await labelMessage({
       gmail: this.client,
       messageId,
-      addLabelIds: unsubscribeLabel?.id ? [unsubscribeLabel.id] : undefined,
+      addLabelIds: [unsubscribeLabel.id],
       removeLabelIds: [GmailLabel.INBOX],
     });
apps/web/utils/actions/rule.ts (1)

546-619: Avoid creating a rule when toggling off an absent system rule.

If the user is turning a status off (enabled === false) and there is no existing system rule, we currently drop into the creation branch and safeCreateRule builds a brand-new (enabled) rule. That makes the toggle behave backwards for those accounts. Add a guard to bail out before provisioning anything when enabled is false and no rule exists.

       if (existingRule) {
         await prisma.rule.update({
           where: { id: existingRule.id },
           data: { enabled },
         });
         return;
       }
 
+      if (!enabled) {
+        return;
+      }
+
       const emailProvider = await createEmailProvider({
         emailAccountId,
         provider,
       });
🧹 Nitpick comments (1)
apps/web/app/api/user/folders/route.ts (1)

22-25: Consider inlining the trivial wrapper function.

The getFolders function now simply calls emailProvider.getFolders() without additional logic. Consider inlining this directly in the GET handler for simplicity.

Apply this diff to inline the function:

 export const GET = withEmailProvider(async (request) => {
   const emailProvider = request.emailProvider;
 
   if (!isMicrosoftProvider(emailProvider.name)) {
     return NextResponse.json(
       { error: "Only Microsoft email providers are supported" },
       { status: 400 },
     );
   }
 
-  const result = await getFolders({ emailProvider });
+  const folders = await emailProvider.getFolders();
-  return NextResponse.json(result);
+  return NextResponse.json(folders);
 });
-
-async function getFolders({ emailProvider }: { emailProvider: EmailProvider }) {
-  const folders = await emailProvider.getFolders();
-  return folders;
-}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8079fe and 7785fce.

📒 Files selected for processing (13)
  • apps/web/app/(app)/accounts/page.tsx (1 hunks)
  • apps/web/app/api/user/folders/route.ts (1 hunks)
  • apps/web/components/FolderCombobox.tsx (0 hunks)
  • apps/web/components/FolderSelector.tsx (2 hunks)
  • apps/web/hooks/useFolders.ts (1 hunks)
  • apps/web/utils/actions/ai-rule.ts (3 hunks)
  • apps/web/utils/actions/rule.ts (13 hunks)
  • apps/web/utils/email/google.ts (5 hunks)
  • apps/web/utils/email/microsoft.ts (7 hunks)
  • apps/web/utils/email/types.ts (5 hunks)
  • apps/web/utils/outlook/errors.ts (1 hunks)
  • apps/web/utils/outlook/folders.ts (3 hunks)
  • apps/web/utils/rule/rule.ts (14 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/components/FolderCombobox.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/web/utils/outlook/errors.ts
🧰 Additional context used
📓 Path-based instructions (28)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/hooks/useFolders.ts
  • apps/web/components/FolderSelector.tsx
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/app/api/user/folders/route.ts
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/components/FolderSelector.tsx
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/hooks/useFolders.ts
  • apps/web/components/FolderSelector.tsx
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/components/FolderSelector.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/hooks/useFolders.ts
  • apps/web/components/FolderSelector.tsx
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
apps/web/app/(app)/*/page.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

apps/web/app/(app)/*/page.tsx: Create new pages at: apps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components so you can load data into them directly

apps/web/app/(app)/*/page.tsx: Create new pages at: apps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components for direct data loading

Files:

  • apps/web/app/(app)/accounts/page.tsx
apps/web/app/(app)/*/**

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/accounts/page.tsx
apps/web/app/(app)/*/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/accounts/page.tsx
apps/web/app/(app)/*/**/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/accounts/page.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/accounts/page.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/hooks/useFolders.ts
  • apps/web/components/FolderSelector.tsx
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/hooks/useFolders.ts
  • apps/web/components/FolderSelector.tsx
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/components/FolderSelector.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/accounts/page.tsx
  • apps/web/components/FolderSelector.tsx
apps/web/hooks/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use SWR for efficient data fetching and caching

apps/web/hooks/**/*.ts: Use SWR for client-side data fetching and caching.
Call mutate() after successful mutations to refresh SWR data on the client.

Files:

  • apps/web/hooks/useFolders.ts
apps/web/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Call mutate() after successful mutations to refresh data

Files:

  • apps/web/hooks/useFolders.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/hooks/useFolders.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/app/api/user/folders/route.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
apps/web/hooks/**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/hooks.mdc)

Place custom hooks in the apps/web/hooks/ directory.

Files:

  • apps/web/hooks/useFolders.ts
apps/web/hooks/use*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/hooks.mdc)

apps/web/hooks/use*.{js,jsx,ts,tsx}: Name custom hooks with the use prefix (e.g., useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer using useSWR.
Create dedicated hooks for specific data types (e.g., useAccounts, useLabels).
Custom hooks should encapsulate reusable stateful logic, especially for data fetching or complex UI interactions.
Keep custom hooks focused on a single responsibility.

Files:

  • apps/web/hooks/useFolders.ts
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use React Hook Form with Zod validation for form handling

Use the LoadingContent component to handle loading and error states consistently in data-fetching components.

Use PascalCase for components (e.g. components/Button.tsx)

Files:

  • apps/web/components/FolderSelector.tsx
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.ts
  • apps/web/utils/actions/ai-rule.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.ts
  • apps/web/utils/actions/ai-rule.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/actions/rule.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/rule/rule.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/email/types.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/user/folders/route.ts
**/api/**/route.ts

📄 CodeRabbit inference engine (.cursor/rules/security.mdc)

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/user/folders/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/user/folders/route.ts
🧠 Learnings (4)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.

Applied to files:

  • apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation

Applied to files:

  • apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/email/google.ts
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.

Applied to files:

  • apps/web/utils/rule/rule.ts
🧬 Code graph analysis (8)
apps/web/app/(app)/accounts/page.tsx (1)
apps/web/components/ui/card.tsx (1)
  • CardContent (143-143)
apps/web/hooks/useFolders.ts (1)
apps/web/app/api/user/folders/route.ts (1)
  • GetFoldersResponse (6-6)
apps/web/utils/actions/rule.ts (8)
apps/web/utils/actions/rule.validation.ts (2)
  • toggleRuleBody (212-220)
  • CategoryAction (192-192)
apps/web/utils/rule/rule.ts (3)
  • createRule (190-252)
  • deleteRule (329-345)
  • safeCreateRule (43-137)
apps/web/utils/rule/consts.ts (3)
  • getRuleConfig (112-116)
  • getCategoryAction (126-134)
  • getSystemRuleActionTypes (277-308)
apps/web/utils/prisma-helpers.ts (1)
  • isDuplicateError (3-12)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/action-item.ts (1)
  • sanitizeActionFields (201-287)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/app/api/user/folders/route.ts (5)
apps/web/utils/email/google.ts (1)
  • getFolders (910-913)
apps/web/utils/email/microsoft.ts (1)
  • getFolders (1288-1290)
apps/web/utils/middleware.ts (1)
  • withEmailProvider (300-304)
apps/web/utils/email/provider-types.ts (1)
  • isMicrosoftProvider (5-7)
apps/web/utils/email/types.ts (1)
  • EmailProvider (44-230)
apps/web/utils/email/google.ts (2)
apps/web/utils/gmail/label.ts (3)
  • labelMessage (129-145)
  • GmailLabel (20-34)
  • labelThread (36-61)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/rule/rule.ts (3)
apps/web/utils/ai/rule/create-rule-schema.ts (1)
  • CreateOrUpdateRuleSchemaWithCategories (163-166)
apps/web/utils/email/provider-types.ts (1)
  • isMicrosoftProvider (5-7)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/email/microsoft.ts (4)
apps/web/utils/outlook/label.ts (1)
  • labelMessage (194-206)
apps/web/utils/outlook/odata-escape.ts (1)
  • escapeODataString (13-20)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/outlook/folders.ts (1)
  • getOutlookFolderTree (83-118)
apps/web/utils/email/types.ts (3)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (11-15)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (5)
apps/web/app/(app)/accounts/page.tsx (1)

92-92: LGTM! Good responsive design improvement.

Adding flex-wrap allows the action buttons to wrap onto multiple lines on smaller viewports, preventing overflow or compression. This is a sensible defensive measure for responsive layouts.

apps/web/app/api/user/folders/route.ts (2)

8-20: LGTM! Provider validation is appropriate.

The Microsoft-only provider check is correct, as Gmail's getFolders() returns an empty array with a warning (see apps/web/utils/email/google.ts:909-912). The 400 status code is appropriate for this client error.


2-4: withEmailProvider correctly handles authentication and scoping. Verified it invokes emailAccountMiddleware and enforces userId/emailAccountId ownership via Prisma—no changes required.

apps/web/components/FolderSelector.tsx (2)

221-234: Consistent conditional logic for the Clear button.

The condition now uses value.id instead of selectedFolder?.displayName, which is consistent with the main display logic at line 211. This allows users to clear the selection even if the folder is not currently in the folders list, improving the user experience in edge cases.


211-218: Approve fallback logic for value.id
Updating the condition to check value.id ensures that when a folder ID is set but not found in folders, the component displays value.name (last-known name) instead of the placeholder, improving resilience. No changes required.

@socket-security
Copy link

socket-security bot commented Oct 12, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​radix-ui/​react-switch@​1.2.6991007095100

View full report

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/utils/actions/rule.ts (1)

248-275: Skip creating a new rule when disabling drafts without an existing rule. toggleRule unconditionally falls back to creating a system rule if none exists, even when enabled is false; add a guard to return early when disabling a non-existent TO_REPLY rule. [apps/web/utils/actions/rule.ts:515–632]

♻️ Duplicate comments (1)
apps/web/utils/actions/rule.ts (1)

557-632: Return early when disabling non-existent system rule.

When enabled is false and no existing rule is found, the function should return early instead of creating a new rule. Currently, it proceeds to create a rule even when the intent is to disable it.

Apply this diff:

   if (existingRule) {
     return await prisma.rule.update({
       where: { id: existingRule.id },
       data: { enabled },
       include: { actions: true },
     });
   }

+  if (!enabled) {
+    // Don't create a new rule when trying to disable a non-existent one
+    return null;
+  }
+
   const emailProvider = await createEmailProvider({
     emailAccountId,
     provider,
   });
🧹 Nitpick comments (1)
apps/web/utils/actions/rule.ts (1)

784-818: Consider avoiding unnecessary label action creation.

The function always creates a label action first (lines 784-786), even for move_folder and move_folder_delayed cases where it's immediately replaced (line 805). While not a bug, you could optimize by checking the category action type first.

   const { label: labelName, labelId } = await resolveLabelNameAndId({
     emailProvider,
     label,
     labelId: null,
   });

-  let actions: Prisma.ActionCreateManyRuleInput[] = [
-    { type: ActionType.LABEL, label: labelName, labelId },
-  ];
+  let actions: Prisma.ActionCreateManyRuleInput[] = [];

   switch (categoryAction) {
     case "label_archive":
     case "label_archive_delayed": {
+      actions.push({ type: ActionType.LABEL, label: labelName, labelId });
       actions.push({
         type: ActionType.ARCHIVE,
         delayInMinutes:
           categoryAction === "label_archive_delayed"
             ? ONE_WEEK_MINUTES
             : undefined,
       });
       break;
     }
+    case "label": {
+      actions.push({ type: ActionType.LABEL, label: labelName, labelId });
+      break;
+    }
     case "move_folder":
     case "move_folder_delayed": {
       const folderId = await emailProvider.getOrCreateOutlookFolderIdByName(
         rule.name,
       );
       actions = [
         {
           type: ActionType.MOVE_FOLDER,
           folderId,
           folderName: rule.name,
           delayInMinutes:
             categoryAction === "move_folder_delayed"
               ? ONE_WEEK_MINUTES
               : undefined,
         },
       ];
       break;
     }
+    default: {
+      // Default case: just label
+      actions.push({ type: ActionType.LABEL, label: labelName, labelId });
+      break;
+    }
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c13a2eb and afe5c45.

📒 Files selected for processing (2)
  • apps/web/utils/actions/rule.ts (14 hunks)
  • apps/web/utils/reply-tracker/enable.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/utils/reply-tracker/enable.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/actions/rule.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/actions/rule.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/actions/rule.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/actions/rule.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/actions/rule.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.

Applied to files:

  • apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation

Applied to files:

  • apps/web/utils/actions/rule.ts
🧬 Code graph analysis (1)
apps/web/utils/actions/rule.ts (8)
apps/web/utils/actions/rule.validation.ts (3)
  • CategoryConfig (205-205)
  • toggleRuleBody (212-220)
  • CategoryAction (192-192)
apps/web/utils/rule/rule.ts (3)
  • createRule (190-252)
  • deleteRule (329-345)
  • safeCreateRule (43-137)
apps/web/utils/rule/consts.ts (3)
  • getRuleConfig (112-116)
  • getCategoryAction (126-134)
  • getSystemRuleActionTypes (277-308)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (62-113)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/action-item.ts (1)
  • sanitizeActionFields (201-287)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (8)
apps/web/utils/actions/rule.ts (8)

1-41: LGTM!

The new imports are correctly added to support the refactored rule management logic, including config-driven rule creation, centralized error handling, and the new toggleRule functionality.


73-73: LGTM!

Good refactor to centralize action field sanitization through mapActionToSanitizedFields. This improves consistency and reduces code duplication.

Also applies to: 183-197


104-104: LGTM!

Good refactor to centralize error handling. The handleRuleError function provides consistent error messages for duplicate name and group ID conflicts.

Also applies to: 216-216, 668-679


307-495: LGTM!

Excellent refactor to use a config-driven approach. The code is more maintainable by:

  • Using Map for system category lookups
  • Iterating over systemRules array instead of explicit per-type logic
  • Leveraging getActionsFromCategoryAction to generate actions consistently

497-513: LGTM!

Clean action wrapper that follows the established pattern for other actions in this file.


635-666: LGTM!

Clean helper function that extracts nested values from action objects and delegates to sanitizeActionFields for consistent field mapping.


681-694: LGTM!

Simple helper that reduces code duplication when creating action fields.


735-750: LGTM!

Good addition to support MOVE_FOLDER actions. The folder resolution logic follows the same pattern as label resolution.

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/utils/actions/rule.ts (1)

248-278: Potential null reference if toggleRule returns undefined.

The toggleRule function can theoretically return undefined based on its implementation (see lines 515-633). If toggling fails or if the rule doesn't exist when disabling, rule could be undefined, leading to a runtime error when accessing rule.actions or rule.id.

Apply this diff to add null checks:

     const rule = await toggleRule({
       emailAccountId,
       enabled: enable,
       systemType: SystemType.TO_REPLY,
       provider,
       ruleId: undefined,
     });

+    if (!rule) {
+      throw new SafeError("Failed to toggle reply tracking rule");
+    }
+
     if (enable) {
       const alreadyDraftingReplies = rule?.actions?.find(
         (a) => a.type === ActionType.DRAFT_EMAIL,
       );
♻️ Duplicate comments (1)
apps/web/utils/webhook/validate-webhook-account.ts (1)

95-98: Critical: Provider can be null, causing runtime errors.

When emailAccount.account?.provider is null or undefined, createEmailProvider will receive an invalid value and throw an error, since its provider parameter is typed as string (non-nullable). This matches the concern raised in the previous review about provider fallback inconsistencies.

The issue is compounded because provider creation occurs before the premium check, meaning the function will crash early when it should proceed to validation and potentially unwatch emails gracefully.

Apply this diff to add a fallback based on the webhook source:

 export async function validateWebhookAccount(
   emailAccount: ValidatedWebhookAccountData | null,
+  providerFallback: "google" | "microsoft",
   logger: {
     error: (message: string, context?: Record<string, unknown>) => void;
     info: (message: string, context?: Record<string, unknown>) => void;
     trace: (message: string, context?: Record<string, unknown>) => void;
   },
 ): Promise<ValidationResult> {
   if (!emailAccount) {
     logger.error("Account not found");
     return { success: false, response: NextResponse.json({ ok: true }) };
   }

   const premium = isPremium(
     emailAccount.user.premium?.lemonSqueezyRenewsAt || null,
     emailAccount.user.premium?.stripeSubscriptionStatus || null,
   )
     ? emailAccount.user.premium
     : undefined;

   const provider = await createEmailProvider({
     emailAccountId: emailAccount.id,
-    provider: emailAccount.account?.provider,
+    provider: emailAccount.account?.provider ?? providerFallback,
   });

Then update the callers to pass the appropriate fallback:

// In apps/web/app/api/google/webhook/process-history.ts
const validation = await validateWebhookAccount(emailAccount, "google", logger);

// In apps/web/app/api/outlook/webhook/process-history.ts
const validation = await validateWebhookAccount(emailAccount, "microsoft", logger);
🧹 Nitpick comments (5)
apps/web/utils/webhook/validate-webhook-account.ts (1)

95-98: Add error handling for provider creation and cleanup operations.

The function lacks error handling around createEmailProvider and unwatchEmails, both of which can throw exceptions. Unhandled errors will return a 500 response to the webhook caller, potentially causing retries and cascading failures.

Consider wrapping these operations in try-catch blocks:

+  let provider: Awaited<ReturnType<typeof createEmailProvider>>;
+  try {
+    provider = await createEmailProvider({
+      emailAccountId: emailAccount.id,
+      provider: emailAccount.account?.provider ?? providerFallback,
+    });
+  } catch (error) {
+    logger.error("Failed to create email provider", {
+      email: emailAccount.email,
+      error: error instanceof Error ? error.message : String(error),
+    });
+    return { success: false, response: NextResponse.json({ ok: true }) };
+  }

   if (!premium) {
     logger.info("Account not premium", { /* ... */ });
-    await unwatchEmails({
-      emailAccountId: emailAccount.id,
-      provider,
-      subscriptionId: emailAccount.watchEmailsSubscriptionId,
-    });
+    try {
+      await unwatchEmails({
+        emailAccountId: emailAccount.id,
+        provider,
+        subscriptionId: emailAccount.watchEmailsSubscriptionId,
+      });
+    } catch (error) {
+      logger.error("Failed to unwatch emails", {
+        email: emailAccount.email,
+        error: error instanceof Error ? error.message : String(error),
+      });
+      // Continue - best effort cleanup
+    }
     return { success: false, response: NextResponse.json({ ok: true }) };
   }

Apply similar wrapping for the AI access check unwatchEmails call.

Also applies to: 108-112, 123-127

apps/web/utils/ai/reply/determine-thread-status.ts (3)

10-18: Add input validation for robustness.

The function should validate that threadMessages is not empty to prevent errors downstream and provide clearer error messages.

As per coding guidelines, add early validation:

 export async function aiDetermineThreadStatus({
   emailAccount,
   threadMessages,
   modelType,
 }: {
   emailAccount: EmailAccountWithAI;
   threadMessages: EmailForLLM[];
   modelType: ModelType;
 }): Promise<{ status: ConversationStatus; rationale: string }> {
+  if (threadMessages.length === 0) {
+    logger.warn("Empty thread messages provided");
+    throw new Error("Thread messages cannot be empty");
+  }
+
   const system = `You are an AI assistant that analyzes email threads to determine their current status.

60-71: Clarify message ordering responsibility.

The comment at Line 62 states "most recent message first" but the function doesn't verify or enforce this ordering. If the caller must ensure this order, consider documenting it in the function's JSDoc or parameter description.

Add a JSDoc comment to document the ordering requirement:

+/**
+ * Determines the status of an email thread using AI analysis.
+ * @param emailAccount - User's email account with AI configuration
+ * @param threadMessages - Email messages in the thread (ordered with most recent first)
+ * @param modelType - Type of AI model to use
+ * @returns The determined status and rationale
+ */
 export async function aiDetermineThreadStatus({

81-89: Consider type consistency between schema and return type.

The Zod schema uses SystemType enum values (Lines 82-87) but the function's return type uses ConversationStatus string union (Line 18). While these may be compatible if SystemType is a string enum, this inconsistency could lead to type safety issues.

For better type safety and maintainability, align the schema with the return type:

 const schema = z.object({
   status: z.enum([
-    SystemType.TO_REPLY,
-    SystemType.FYI,
-    SystemType.AWAITING_REPLY,
-    SystemType.ACTIONED,
+    "TO_REPLY",
+    "FYI", 
+    "AWAITING_REPLY",
+    "ACTIONED",
-  ]),
+  ] as const),
   rationale: z.string(),
 });

Alternatively, if SystemType should be used throughout, update the return type and ConversationStatus type to use SystemType instead of a string union.

apps/web/utils/actions/rule.ts (1)

756-829: Consider a more precise type for the rule parameter.

The rule parameter is typed as Rule from Prisma, but the function only accesses rule.name. In createRulesOnboardingAction, this is called with mock objects like { name: customCategory.name } as Rule, which is a type assertion that bypasses type safety.

Consider defining a narrower interface:

+interface RuleNameOnly {
+  name: string;
+}
+
 async function getActionsFromCategoryAction({
   emailAccountId,
   rule,
   categoryAction,
   label,
   draftReply,
   hasDigest,
   provider,
 }: {
   emailAccountId: string;
-  rule: Rule;
+  rule: RuleNameOnly;
   categoryAction: CategoryAction;
   label: string;
   hasDigest: boolean;
   draftReply: boolean;
   provider: string;
 }): Promise<Prisma.ActionCreateManyRuleInput[]> {

This makes the function's requirements explicit and eliminates the need for type assertions at call sites.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c13a2eb and 636b86c.

📒 Files selected for processing (5)
  • apps/web/utils/actions/rule.ts (14 hunks)
  • apps/web/utils/ai/reply/determine-thread-status.ts (1 hunks)
  • apps/web/utils/reply-tracker/enable.ts (0 hunks)
  • apps/web/utils/reply-tracker/handle-conversation-status.ts (1 hunks)
  • apps/web/utils/webhook/validate-webhook-account.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/utils/reply-tracker/enable.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/utils/reply-tracker/handle-conversation-status.ts
🧰 Additional context used
📓 Path-based instructions (12)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/webhook/validate-webhook-account.ts
  • apps/web/utils/actions/rule.ts
  • apps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/rule.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/reply/determine-thread-status.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.

Applied to files:

  • apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation

Applied to files:

  • apps/web/utils/actions/rule.ts
🧬 Code graph analysis (3)
apps/web/utils/webhook/validate-webhook-account.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/premium/index.ts (2)
  • isPremium (14-22)
  • hasAiAccess (87-101)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/actions/rule.ts (10)
apps/web/utils/actions/rule.validation.ts (2)
  • toggleRuleBody (212-220)
  • CategoryAction (192-192)
apps/web/utils/rule/rule.ts (3)
  • createRule (190-252)
  • deleteRule (329-345)
  • safeCreateRule (43-137)
apps/web/utils/rule/consts.ts (3)
  • getRuleConfig (112-116)
  • getCategoryAction (126-134)
  • getSystemRuleActionTypes (277-308)
apps/web/utils/prisma-helpers.ts (1)
  • isDuplicateError (3-12)
apps/web/utils/actions/safe-action.ts (1)
  • actionClient (62-113)
apps/web/utils/error.ts (1)
  • SafeError (86-96)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/label/resolve-label.ts (1)
  • resolveLabelNameAndId (13-65)
apps/web/utils/action-item.ts (1)
  • sanitizeActionFields (201-287)
apps/web/utils/logger.ts (1)
  • Logger (5-5)
apps/web/utils/ai/reply/determine-thread-status.ts (6)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/types.ts (1)
  • EmailForLLM (117-131)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • ConversationStatus (10-14)
apps/web/utils/ai/helpers.ts (2)
  • getUserInfoPrompt (9-35)
  • getEmailListPrompt (54-68)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (7)
apps/web/utils/webhook/validate-webhook-account.ts (1)

108-112: Verify the selective unwatchEmails pattern is intentional.

The function calls unwatchEmails when premium or AI access is missing but skips it when automation rules are absent or tokens are invalid. This inconsistency might be intentional (e.g., rules and tokens are temporary conditions that could self-resolve), but it should be verified.

If this pattern is intentional, consider adding comments to clarify why cleanup is selective:

// No need to unwatch - user may add rules later
if (!hasAutomationRules) {
  logger.trace("Has no rules enabled", { email: emailAccount.email });
  return { success: false, response: NextResponse.json({ ok: true }) };
}

// No need to unwatch - tokens may be refreshed via OAuth flow
if (!emailAccount.account?.access_token || !emailAccount.account?.refresh_token) {
  logger.error("Missing access or refresh token", { email: emailAccount.email });
  return { success: false, response: NextResponse.json({ ok: true }) };
}

Otherwise, consider unwatching in all failure cases for consistency.

Also applies to: 123-127, 132-135, 137-145

apps/web/utils/ai/reply/determine-thread-status.ts (1)

19-58: LGTM! Well-structured system prompt.

The system prompt is comprehensive and well-designed with:

  • Clear status definitions with concrete examples
  • Critical rules section to prevent common classification mistakes
  • Emphasis on checking the entire thread, not just the latest message
  • Explicit JSON response format

This thoughtful prompt structure should help the LLM make accurate status determinations.

apps/web/utils/actions/rule.ts (5)

437-454: LGTM! System rules loop is correctly implemented.

The systemType-driven loop correctly uses UPPER_SNAKE_CASE enum values and properly handles creation/deletion based on configuration.


497-513: LGTM! Action correctly delegates to internal toggleRule.

The action properly validates inputs via toggleRuleBody schema and delegates to the internal toggleRule function. Error handling is managed by the next-safe-action framework.


635-666: LGTM! Helper correctly maps nested action fields.

The mapActionToSanitizedFields helper properly extracts nested field values and delegates to sanitizeActionFields for consistent field normalization.


668-694: LGTM! Error handling and field creation helpers are well-designed.

Both handleRuleError and createEmptyActionFields are clean, focused utility functions:

  • handleRuleError properly uses SafeError for consistent error handling
  • createEmptyActionFields provides a convenient way to create empty action field objects with optional overrides

696-754: LGTM! Folder resolution follows the same pattern as label resolution.

The added MOVE_FOLDER handling correctly resolves folder IDs using the email provider, mirroring the existing label resolution logic. The type definitions are consistent with the existing pattern.

Comment on lines +1 to +8
import { z } from "zod";
import { createGenerateObject } from "@/utils/llms";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import type { EmailForLLM } from "@/utils/types";
import { getModel, type ModelType } from "@/utils/llms/model";
import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config";
import { SystemType } from "@prisma/client";
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add scoped logger for observability.

Per coding guidelines, backend TypeScript files should use createScopedLogger, and AI feature functions should log inputs and outputs with appropriate log levels.

As per coding guidelines, add a scoped logger at the top of the file:

+import { createScopedLogger } from "@/utils/logger";
 import { z } from "zod";
 import { createGenerateObject } from "@/utils/llms";
 import type { EmailAccountWithAI } from "@/utils/llms/types";
 import type { EmailForLLM } from "@/utils/types";
 import { getModel, type ModelType } from "@/utils/llms/model";
 import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
 import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config";
 import { SystemType } from "@prisma/client";
+
+const logger = createScopedLogger("aiDetermineThreadStatus");

Then add logging at key points within the function (see subsequent comments).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { z } from "zod";
import { createGenerateObject } from "@/utils/llms";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import type { EmailForLLM } from "@/utils/types";
import { getModel, type ModelType } from "@/utils/llms/model";
import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config";
import { SystemType } from "@prisma/client";
import { createScopedLogger } from "@/utils/logger";
import { z } from "zod";
import { createGenerateObject } from "@/utils/llms";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import type { EmailForLLM } from "@/utils/types";
import { getModel, type ModelType } from "@/utils/llms/model";
import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config";
import { SystemType } from "@prisma/client";
const logger = createScopedLogger("aiDetermineThreadStatus");
🤖 Prompt for AI Agents
In apps/web/utils/ai/reply/determine-thread-status.ts around lines 1 to 8, add a
scoped logger and instrumentation: import createScopedLogger from the shared
logging utility, create a logger instance for this module (e.g., const logger =
createScopedLogger("ai:determine-thread-status")), then instrument the main
function(s) to log incoming inputs at debug/info when the function is entered,
log key decision points and constructed prompts/models at debug, log the final
returned thread status at info, and log any caught errors at error with the
error object; keep logs concise and avoid sensitive data.

@elie222 elie222 merged commit 7381f3e into main Oct 12, 2025
4 of 5 checks passed
@elie222 elie222 deleted the feat/enhanced-reply-tracking branch December 18, 2025 23:02
@coderabbitai coderabbitai bot mentioned this pull request Dec 22, 2025
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

Comments