Skip to content

Better typing for microsoft client#738

Merged
elie222 merged 4 commits intomainfrom
feat/better-microsoft-client-typing
Sep 1, 2025
Merged

Better typing for microsoft client#738
elie222 merged 4 commits intomainfrom
feat/better-microsoft-client-typing

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Sep 1, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Draft deletion is more resilient; missing drafts no longer cause failures.
    • Folder browsing is more reliable with consistent child-folder handling and safer IDs/names.
    • Filter management improved with clearer error handling and consistent delete behavior.
    • Email label and draft ID returns now consistently provide string values.
  • Refactor

    • Stronger typing across Outlook/Microsoft integrations for more robust mail, thread, attachment, and label handling.
    • Bulk unsubscribe flows and callbacks tightened for safer premium-related behavior.
  • Chores

    • Linter now warns on explicit any usage.
    • Version bumped to v2.7.4.

@vercel
Copy link

vercel bot commented Sep 1, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Sep 1, 2025 0:57am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds Microsoft Graph types across Outlook/email utilities, tightens several function and prop typings, changes deleteDraft to ignore 404s (log warning), changes deleteFilter to return only status, refactors OutlookFolder to derive fields from MailFolder via a converter, adjusts several mutate/refetchPremium signatures, and enables a biome noExplicitAny warning.

Changes

Cohort / File(s) Summary of Changes
Graph typing hardening (Message / Attachment / Label / Mail / MessageBatch / Thread / Draft)
apps/web/utils/outlook/attachment.ts, apps/web/utils/outlook/mail.ts, apps/web/utils/outlook/message.ts, apps/web/utils/outlook/thread.ts, apps/web/utils/outlook/label.ts, apps/web/utils/outlook/draft.ts, apps/web/utils/email/microsoft.ts
Add type-only imports from @microsoft/microsoft-graph-types; annotate API results with Message, FileAttachment, and OData shapes ({ value: Message[]; "@odata.nextLink"?: string }); tighten null/undefined handling and mapping (e.g., `result.id
Filters typing + API shape tweak
apps/web/utils/outlook/filter.ts, apps/web/utils/email/microsoft.ts
Import OutlookCategory/MessageRule; explicitly type create/update/list responses; deleteFilter now returns { status: 204 } (removed data payload); adjust category typing and error extraction.
Folders type refactor & mapping
apps/web/utils/outlook/folders.ts
Import MailFolder; change exported OutlookFolder fields to derive types from MailFolder (NonNullable<...>); add convertMailFolderToOutlookFolder and map API responses through it in root/child/tree functions.
Bulk-unsubscribe typing and lint suppressions
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/*, apps/web/app/(app)/[emailAccountId]/assistant/*
Tighten refetchPremium types to `Promise<UserResponse
ErrorBoundary and form typing
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx, apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
Replace any with concrete error typing for ErrorBoundary (Error & { digest?: string }); type ActionCard errors as FieldErrors<CreateRuleBody> and add a small fallback message for delay errors.
Resend contacts signature cleanup
packages/resend/src/contacts.ts
Remove explicit Promise<any> return annotations from createContact and deleteContact (rely on inferred async return type).
Script robustness change
apps/web/scripts/addUsersToResend.ts
Replace destructured createContact result with guarded extraction of error to avoid exceptions when result shape is unexpected.
Linter rule tweak
biome.json
Change linter.suspicious.noExplicitAny severity from "off" to "warn" (enable warnings for explicit any).
Minor signature change (OutlookProvider)
apps/web/utils/email/microsoft.ts
Remove explicit Promise<any> return type from createFilter (type now inferred).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Caller
  participant App as App Service
  participant Graph as Microsoft Graph

  Client->>App: deleteDraft(draftId)
  App->>Graph: DELETE /me/messages/{draftId}
  alt 204 No Content
    Graph-->>App: 204
    App-->>Client: resolves (logs success)
  else 404 Not Found
    Graph-->>App: 404
    App-->>Client: resolves (logs warning)
  else Other Error
    Graph-->>App: 4xx/5xx Error
    App-->>Client: throws (logs error)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

I twitch my ears at typed delight,
OData crumbs beneath moonlight.
Folders mapped and ids made sure,
Drafts that vanish—warn, not cure.
I hop through lines with tidy cheer,
Typed and tidy — rabbit near. 🐇✨


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f72553f and fa409ac.

📒 Files selected for processing (3)
  • apps/web/utils/outlook/folders.ts (6 hunks)
  • apps/web/utils/outlook/thread.ts (6 hunks)
  • version.txt (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/better-microsoft-client-typing

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

⚠️ Outside diff range comments (7)
apps/web/utils/outlook/draft.ts (1)

26-36: Same 404 handling issue in deleteDraft.

Check statusCode === 404 or code === "ErrorItemNotFound"; otherwise we’ll rethrow on not-found.

Apply this diff:

   try {
     logger.info("Deleting draft", { draftId });
     await client.getClient().api(`/me/messages/${draftId}`).delete();
     logger.info("Successfully deleted draft", { draftId });
   } catch (error) {
-    if (error instanceof Error && "code" in error && error.code === 404) {
+    const anyErr = error as { statusCode?: number; code?: string };
+    if (anyErr?.statusCode === 404 || anyErr?.code === "ErrorItemNotFound") {
       logger.warn("Draft not found or already deleted, skipping deletion.", {
         draftId,
       });
     } else {
       logger.error("Failed to delete draft", { draftId, error });
       throw error;
     }
   }
apps/web/utils/outlook/thread.ts (1)

10-15: Escape threadId in OData filter to avoid breaking queries.

Quotes in conversationId will break the filter and can be abused. Use the existing escape helper.

Apply this diff:

   const messages: { value: Message[] } = await client
     .getClient()
     .api("/me/messages")
-    .filter(`conversationId eq '${threadId}'`)
+    .filter(`conversationId eq '${escapeODataString(threadId)}'`)
     .orderby("receivedDateTime desc")
     .get();
apps/web/utils/outlook/message.ts (2)

247-257: Pagination bug: hasMore computed from filtered messages, not raw page size.

Because convertMessages filters out drafts, messages.length can be < maxResults even when more pages exist. Compute hasMore from response.value.length instead.

Apply this diff:

-    // For non-search, calculate next page token based on message count
-    const hasMore = messages.length === maxResults;
+    // For non-search, calculate next page token based on raw page size
+    const hasMore = response.value.length === maxResults;

306-323: pageToken is ignored in getMessages.

The function returns nextPageToken but never consumes options.pageToken, so callers can’t paginate. Support both raw skiptoken and a full @odata.nextLink URL.

Apply this diff:

   let request = client
     .getClient()
     .api("/me/messages")
     .top(top)
     .select(
       "id,conversationId,conversationIndex,subject,bodyPreview,body,from,toRecipients,receivedDateTime,isRead,categories,parentFolderId,isDraft",
     );

   if (options.query) {
     request = request.filter(
       `contains(subject, '${escapeODataString(options.query)}')`,
     );
   }
 
+  // Respect pageToken: accept either a raw $skiptoken or a full @odata.nextLink
+  if (options.pageToken) {
+    let token = options.pageToken;
+    try {
+      const url = new URL(options.pageToken);
+      token = url.searchParams.get("$skiptoken") ?? options.pageToken;
+    } catch {
+      // Not a URL; treat as raw skiptoken
+    }
+    request = request.skipToken(token);
+  }
+
   const response: { value: Message[]; "@odata.nextLink"?: string } =
     await request.get();
apps/web/utils/outlook/filter.ts (3)

31-36: moveToFolder must be a folder ID, not the string "archive".

Graph messageRules.actions.moveToFolder expects a folder ID. Using "archive" will fail at runtime. Fetch the Archive folder ID first.

Apply this diff (and helper):

+async function getArchiveFolderId(client: OutlookClient): Promise<string> {
+  const { id } = await client
+    .getClient()
+    .api("/me/mailFolders/archive")
+    .select("id")
+    .get();
+  return id;
+}
 export async function createFilter(options: {
   client: OutlookClient;
   from: string;
   addLabelIds?: string[];
   removeLabelIds?: string[];
 }) {
   const { client, from, removeLabelIds } = options;

   try {
-    // Create a mail rule that moves messages from specific sender
+    // Create a mail rule that moves messages from specific sender
+    const archiveFolderId =
+      removeLabelIds?.includes("INBOX") ? await getArchiveFolderId(client) : undefined;
     const rule: MessageRule = {
       displayName: `Filter for ${from}`,
       sequence: 1,
       isEnabled: true,
       conditions: {
         senderContains: [from],
       },
       actions: {
-        moveToFolder: removeLabelIds?.includes("INBOX") ? "archive" : undefined,
+        moveToFolder: archiveFolderId,
         markAsRead: removeLabelIds?.includes("UNREAD") ? true : undefined,
         // Categories would need to be handled separately
       },
     };

71-76: Same issue: use Archive folder ID for auto-archive rule.

Apply this diff:

   try {
     // For Outlook, we'll create a rule that moves messages to archive
+    const archiveFolderId = await getArchiveFolderId(client);
     const rule: MessageRule = {
       displayName: `Auto-archive filter for ${from}`,
       sequence: 1,
       isEnabled: true,
       conditions: {
         senderContains: [from],
       },
       actions: {
-        moveToFolder: "archive",
+        moveToFolder: archiveFolderId,
         markAsRead: true,
         ...(labelName && { assignCategories: [labelName] }),
       },
     };

211-222: Same moveToFolder ID issue in updateFilter.

Apply this diff:

   try {
-    const rule: MessageRule = {
+    const archiveFolderId =
+      removeLabelIds?.includes("INBOX") ? await getArchiveFolderId(client) : undefined;
+    const rule: MessageRule = {
       displayName: `Filter for ${from}`,
       sequence: 1,
       isEnabled: true,
       conditions: {
         senderContains: [from],
       },
       actions: {
-        moveToFolder: removeLabelIds?.includes("INBOX") ? "archive" : undefined,
+        moveToFolder: archiveFolderId,
         markAsRead: removeLabelIds?.includes("UNREAD") ? true : undefined,
       },
     };

Also applies to: 224-227

🧹 Nitpick comments (5)
apps/web/utils/outlook/message.ts (1)

328-331: Standardize nextPageToken shape across APIs.

queryBatchMessages returns the $skiptoken, but getMessages returns the full nextLink. Consider normalizing to always return the raw skiptoken for consistency.

apps/web/utils/outlook/filter.ts (2)

131-137: Harden duplicate detection using Graph error shape/status.

String matching is brittle. Also check status 409 and common Graph error codes.

Apply this diff:

-function isFilterExistsError(error: unknown): boolean {
-  const errorMessage = (error as unknown as { message: string })?.message || "";
-  return (
-    errorMessage.includes("already exists") ||
-    errorMessage.includes("duplicate") ||
-    errorMessage.includes("conflict")
-  );
-}
+function isFilterExistsError(error: unknown): boolean {
+  const anyErr = error as any;
+  const status = anyErr?.statusCode ?? anyErr?.status;
+  const code = anyErr?.code ?? anyErr?.error?.code;
+  const message: string =
+    anyErr?.message ?? anyErr?.error?.message ?? "";
+  return (
+    status === 409 ||
+    code === "nameAlreadyExists" ||
+    code === "ErrorItemAlreadyExists" ||
+    /already exists|duplicate|conflict/i.test(message)
+  );
+}

153-171: Consider actually creating a rule that assigns the category.

You already use actions.assignCategories elsewhere. Here you only create the category. Optionally add a rule to assign it for messages from the sender.

Example change:

const rule: MessageRule = {
  displayName: `Assign category ${categoryName} for ${from}`,
  sequence: 1,
  isEnabled: true,
  conditions: { senderContains: [from] },
  actions: { assignCategories: [categoryName] },
};
await client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule);

Also applies to: 177-189

apps/web/utils/outlook/folders.ts (2)

70-75: Avoid non-null assertion when shifting from the queue.

Comply with “Don’t use non-null assertions” guideline and keep types safe.

Apply this diff:

-    const folderQueue = [...folders];
+    const folderQueue: OutlookFolder[] = [...folders];
 
-    while (folderQueue.length > 0) {
-      const folder = folderQueue.shift()!;
+    let folder: OutlookFolder | undefined;
+    while ((folder = folderQueue.shift())) {

108-113: Type the create-folder response.

Keep typing consistent with Graph types.

Apply this diff:

-  const response = await client.getClient().api("/me/mailFolders").post({
-    displayName: folderName,
-  });
+  const response: MailFolder = await client
+    .getClient()
+    .api("/me/mailFolders")
+    .post({ displayName: folderName });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 19631e7 and 412cb34.

📒 Files selected for processing (9)
  • apps/web/utils/outlook/attachment.ts (1 hunks)
  • apps/web/utils/outlook/draft.ts (3 hunks)
  • apps/web/utils/outlook/filter.ts (9 hunks)
  • apps/web/utils/outlook/folders.ts (6 hunks)
  • apps/web/utils/outlook/label.ts (5 hunks)
  • apps/web/utils/outlook/mail.ts (4 hunks)
  • apps/web/utils/outlook/message.ts (4 hunks)
  • apps/web/utils/outlook/thread.ts (6 hunks)
  • biome.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
!{.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:

  • biome.json
  • apps/web/utils/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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:

  • biome.json
  • apps/web/utils/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.ts
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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.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/outlook/attachment.ts
  • apps/web/utils/outlook/mail.ts
  • apps/web/utils/outlook/message.ts
  • apps/web/utils/outlook/draft.ts
  • apps/web/utils/outlook/folders.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/outlook/thread.ts
  • apps/web/utils/outlook/filter.ts
🧬 Code graph analysis (4)
apps/web/utils/outlook/attachment.ts (1)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-63)
apps/web/utils/outlook/folders.ts (1)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-63)
apps/web/utils/outlook/label.ts (1)
apps/web/utils/email/microsoft.ts (1)
  • labelMessage (262-272)
apps/web/utils/outlook/thread.ts (1)
apps/web/utils/outlook/odata-escape.ts (1)
  • escapeODataString (12-18)
⏰ 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: Secret Detection
  • GitHub Check: Jit Security
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (15)
apps/web/utils/outlook/mail.ts (2)

1-1: LGTM on typed Message results.

The added Message typings for create/forward/draft improve safety without changing behavior.

Also applies to: 50-53, 112-115, 209-212


46-48: conversationId is read-only on Message; verify this assignment is tolerated.

Microsoft Graph treats conversationId as read-only; setting it on create may be ignored or rejected. Please confirm this doesn’t trigger 4xx responses and remove if unnecessary.

Would you like me to patch this by dropping the assignment and relying on Graph to thread the conversation automatically?

apps/web/utils/outlook/label.ts (2)

57-64: LGTM: typed category list shape.

Explicit { value: OutlookCategory[] } typing and the name derivation look good.


72-77: LGTM: direct OutlookCategory typing for get/create.

Removing casts and relying on Graph types improves clarity.

Also applies to: 95-103

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

77-78: LGTM on OData paging/types and narrowed message arrays.

Typed OData shapes and consistent Message usage look good.

Also applies to: 102-109, 129-136, 159-159

biome.json (1)

27-27: LGTM: Appropriate linting policy change

Changing noExplicitAny from "off" to "warn" aligns well with the TypeScript improvements being made across the Outlook utilities. This will help catch future instances of untyped any usage while not being overly strict by blocking builds.

apps/web/utils/outlook/message.ts (2)

184-186: Typed nextLink handling is good.

Explicitly typing the Graph page shape with optional "@odata.nextLink" is clear and safe.


208-215: Search branch response typing looks good.

Consistent with the filter branch and facilitates token extraction.

apps/web/utils/outlook/filter.ts (5)

2-5: Stronger typing import is good.

Using MessageRule and OutlookCategory from Graph types improves safety.


38-41: Response typing LGTM.

Returning the created rule as MessageRule is appropriate.


78-81: Response typing LGTM.


114-117: Typed list response LGTM.

{ value: MessageRule[] } matches Graph paging.


100-106: Verified no callers use response.data
All deleteFilter call sites only inspect res.status and never access res.data, so omitting the data is safe.

apps/web/utils/outlook/folders.ts (2)

29-36: LGTM on typed responses and mapping.

Also applies to: 38-39


46-53: LGTM on typed responses and mapping for child folders.

Also applies to: 55-56

Comment on lines +10 to 13
const response: Message = await client
.getClient()
.api(`/me/messages/${draftId}`)
.get();
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

404 detection uses the wrong error property; use statusCode or Graph code string.

Graph errors expose statusCode (number) and code (string like "ErrorItemNotFound"). Comparing error.code === 404 will miss 404s and rethrow.

Apply this diff:

   try {
-    const response: Message = await client
+    const response: Message = await client
       .getClient()
       .api(`/me/messages/${draftId}`)
       .get();
     const message = convertMessage(response);
     return message;
   } catch (error) {
-    if (error instanceof Error && "code" in error && error.code === 404)
-      return null;
+    const anyErr = error as { statusCode?: number; code?: string };
+    if (anyErr?.statusCode === 404 || anyErr?.code === "ErrorItemNotFound") {
+      return null;
+    }
     throw error;
   }

Also applies to: 17-19

🤖 Prompt for AI Agents
In apps/web/utils/outlook/draft.ts around lines 10-13 (and also update lines
17-19), the 404 detection compares error.code to numeric 404 which is incorrect;
Graph errors expose statusCode (number) and code (string like
"ErrorItemNotFound"). Change the error handling to detect 404s by checking
error.statusCode === 404 or by matching error.code === "ErrorItemNotFound" (or
both), then handle the not-found case instead of rethrowing; leave other errors
rethrown. Ensure both try/catch blocks use the same corrected detection.

Comment on lines 216 to 221
const escapedThreadId = threadId.replace(/'/g, "''");
const messages = await client
const messages: { value: Message[] } = await client
.getClient()
.api("/me/messages")
.filter(`conversationId eq '${escapedThreadId}'`)
.get();
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

Remove non-null assertion on message.id and select only needed fields.

Non-null assertions are disallowed in the guidelines and can mask data issues. Filter safely and request only id.

Apply this diff:

-  const escapedThreadId = threadId.replace(/'/g, "''");
-  const messages: { value: Message[] } = await client
+  const escapedThreadId = threadId.replace(/'/g, "''");
+  const messages: { value: Array<Pick<Message, "id">> } = await client
     .getClient()
     .api("/me/messages")
-    .filter(`conversationId eq '${escapedThreadId}'`)
-    .get();
+    .filter(`conversationId eq '${escapedThreadId}'`)
+    .select("id")
+    .get();
 
   await Promise.all(
-    messages.value.map((message) =>
-      labelMessage({ client, messageId: message.id!, categories }),
-    ),
+    messages.value
+      .filter((m): m is { id: string } => typeof m.id === "string")
+      .map((m) => labelMessage({ client, messageId: m.id, categories })),
   );

Optional: import and use escapeODataString instead of manual replace for consistency across modules.

Also applies to: 224-226

🤖 Prompt for AI Agents
In apps/web/utils/outlook/label.ts around lines 216-221 (and also apply same
change to 224-226), stop using non-null assertions on message.id and only
request the id field from Graph: use the client's OData select to request id
only (e.g., .select('id')) when fetching messages, remove any "!" assertions and
instead safely handle possible undefined ids (skip or filter out messages
without an id), and replace the manual threadId.replace(...) with
escapeODataString(threadId) for safer OData escaping (import escapeODataString
where needed).

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

Caution

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

⚠️ Outside diff range comments (3)
packages/resend/src/contacts.ts (2)

16-27: Same here: keep return type narrow and remove console use

Mirror the change for deleteContact to avoid Promise<void | T> and adhere to logging guidelines.

 export async function deleteContact(options: {
   email: string;
   audienceId?: string;
 }) {
-  if (!resend) {
-    console.warn("Resend not configured");
-    return;
-  }
+  if (!resend) {
+    throw new Error("Resend client not configured");
+  }
   const audienceId = process.env.RESEND_AUDIENCE_ID || options.audienceId;
   if (!audienceId) throw new Error("Missing audienceId");
   return resend.contacts.remove({ email: options.email, audienceId });
 }

3-14: Throw when Resend client is not configured and remove console.warn
The early return; widens the return type to Promise<void | T> and console.warn violates our no-console rule. Replace in both createContact and deleteContact:

if (!resend) {
-  console.warn("Resend not configured");
-  return;
+  throw new Error("Resend client not configured");
}
apps/web/utils/email/microsoft.ts (1)

805-813: Escape single quotes in OData filter to prevent breakage/injection

options.from can contain '. Escape for OData.

-        .filter(
-          `from/emailAddress/address eq '${options.from}' and receivedDateTime lt ${options.date.toISOString()}`,
-        )
+        .filter(() => {
+          const fromEsc = options.from.replace(/'/g, "''");
+          return `from/emailAddress/address eq '${fromEsc}' and receivedDateTime lt ${options.date.toISOString()}`;
+        })
🧹 Nitpick comments (12)
apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx (2)

179-182: Remove any and biome-ignore by building params type-safely

Avoid as any and the lint suppression. Build URLSearchParams from typed entries.

Apply within this block:

-        url: `/api/threads?${
-          // biome-ignore lint/suspicious/noExplicitAny: simplest
-          new URLSearchParams(query as any).toString()
-        }`,
+        url: `/api/threads?${toSearchParams(query)}`,

Add this helper near the file bottom (or above run()):

function toSearchParams(q: ThreadsQuery): string {
  const params = new URLSearchParams();
  for (const [k, v] of Object.entries(q)) {
    if (v !== undefined && v !== null && v !== "") params.set(k, String(v));
  }
  return params.toString();
}

46-46: Type abortRef precisely

Prefer a nullable ref to avoid undefined initial value.

-  const abortRef = useRef<() => void>(undefined);
+  const abortRef = useRef<(() => void) | null>(null);
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)

256-265: Replace console.error with Sentry (project guideline: don’t use console)

Capture errors instead of logging to console.

+import { captureException } from "@sentry/nextjs";
@@
-          console.error(res);
+          captureException(new Error(res.serverError ?? "updateRuleAction failed"));
@@
-          console.error(res);
+          captureException(new Error(res.serverError ?? "createRuleAction failed"));

Also applies to: 288-295

apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx (1)

3-3: Avoid namespace import for Sentry (house style)

Switch to named import to comply with “no namespace imports”.

-import * as Sentry from "@sentry/nextjs";
+import { captureException } from "@sentry/nextjs";
@@
-  useEffect(() => {
-    Sentry.captureException(error);
-  }, [error]);
+  useEffect(() => {
+    captureException(error);
+  }, [error]);

Also applies to: 12-14

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

462-468: Add explicit return types to avoid implicit any drift

Document the shapes returned by the Outlook filter helpers.

+import type { MessageRule } from "@microsoft/microsoft-graph-types";
@@
-  async createFilter(options: {
+  async createFilter(options: {
     from: string;
     addLabelIds?: string[];
     removeLabelIds?: string[];
-  }) {
+  }): Promise<{ status: number; data?: MessageRule }> {
     return createFilter({ client: this.client, ...options });
   }
@@
-  async createAutoArchiveFilter(options: { from: string; labelName?: string }) {
+  async createAutoArchiveFilter(options: { from: string; labelName?: string }): Promise<{ status: number; data?: MessageRule }> {
     return createAutoArchiveFilter({
       client: this.client,
       from: options.from,
       labelName: options.labelName,
     });
   }
@@
-  async deleteFilter(id: string) {
+  async deleteFilter(id: string): Promise<{ status: number }> {
     return deleteFilter({ client: this.client, id });
   }

Also applies to: 470-476, 478-480

apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1)

110-111: Remove any-cast; serialize params in a typed way

Avoid the biome-ignore and any by constructing URLSearchParams explicitly, including array keys.

-  // biome-ignore lint/suspicious/noExplicitAny: simplest
-  const urlParams = new URLSearchParams(params as any);
+  const urlParams = toNewsletterStatsSearchParams(params);

Add helper (outside this hunk), typed to NewsletterStatsQuery:

function toNewsletterStatsSearchParams(params: NewsletterStatsQuery) {
  const sp = new URLSearchParams();
  params.types?.forEach((t) => sp.append("types", t));
  params.filters?.forEach((f) => sp.append("filters", f));
  if (params.orderBy) sp.set("orderBy", params.orderBy);
  if (params.limit != null) sp.set("limit", String(params.limit));
  if (params.includeMissingUnsubscribe != null) {
    sp.set("includeMissingUnsubscribe", String(params.includeMissingUnsubscribe));
  }
  if ((params as any).from) sp.set("from", String((params as any).from));
  if ((params as any).to) sp.set("to", String((params as any).to));
  return sp;
}
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts (1)

4-4: Standardize mutate return type to Promise<void> across bulk-unsubscribe
Change all occurrences of

mutate: () => Promise<any>;

(in types.ts:26, hooks.ts:607, BulkActions.tsx:26) to

mutate: () => Promise<void>;

and remove the // biome-ignore lint/suspicious/noExplicitAny suppression.

apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx (1)

25-26: Replace any with a safer return type; drop the ignore

If callers pass SWR’s mutate directly, prefer Promise here to satisfy noExplicitAny without forcing wrappers.

-  // biome-ignore lint/suspicious/noExplicitAny: lazy
-  mutate: () => Promise<any>;
+  mutate: () => Promise<unknown>;

Alternative (preferred long-term): switch to Promise and wrap at call site:

// <BulkActions mutate={async () => { await mutate(); }} />
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (1)

21-21: LGTM on refetchPremium typing; consider decoupling type import

Tightening to Promise<UserResponse | null | undefined> looks good. Optional: re-export UserResponse from a domain types module to avoid coupling UI to route types.

Also applies to: 73-74, 163-164, 227-228

apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (3)

132-140: Reduce refetch storms: call mutate once per bulk action

You’re calling mutate for each item in bulk loops. For SWR this triggers multiple revalidations. Prefer updating all items, then a single mutate at the end.

Example sketch:

for (const item of items) {
  // do work without mutate
}
await mutate();

For archive/delete helpers that accept onFinish, collect completions and mutate once when all settle.

Also applies to: 302-311, 479-483, 578-583


604-607: Unify mutate type in shortcuts; remove any + ignore

Make mutate here match the rest of the file (Promise) and drop the ignore. If the caller passes SWR’s mutate, wrap it there to return void.

-  refetchPremium: () => Promise<UserResponse | null | undefined>;
+  refetchPremium: () => Promise<UserResponse | null | undefined>;
   hasUnsubscribeAccess: boolean;
-  // biome-ignore lint/suspicious/noExplicitAny: simplest
-  mutate: () => Promise<any>;
+  mutate: () => Promise<void>;

Supporting call site (in BulkUnsubscribeSection):

useBulkUnsubscribeShortcuts({
  // ...
  mutate: async () => { await mutate(); },
});

336-343: Optional API ergonomics: make refetchPremium optional in useAutoArchive

ApproveButton passes a no-op refetch. Consider making refetchPremium optional and guarding its call to simplify usage.

-export function useAutoArchive<T extends Row>({ ..., refetchPremium, ... }:{
+export function useAutoArchive<T extends Row>({ ..., refetchPremium, ... }:{
   // ...
-  refetchPremium: () => Promise<UserResponse | null | undefined>;
+  refetchPremium?: () => Promise<UserResponse | null | undefined>;

And guard:

await refetchPremium?.();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 412cb34 and f72553f.

📒 Files selected for processing (12)
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (12 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts (2 hunks)
  • apps/web/scripts/addUsersToResend.ts (1 hunks)
  • apps/web/utils/email/microsoft.ts (8 hunks)
  • apps/web/utils/outlook/filter.ts (9 hunks)
  • packages/resend/src/contacts.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/utils/outlook/filter.ts
🧰 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/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/scripts/addUsersToResend.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.ts
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/scripts/addUsersToResend.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • packages/resend/src/contacts.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/scripts/addUsersToResend.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • packages/resend/src/contacts.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.ts
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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/scripts/addUsersToResend.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • packages/resend/src/contacts.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/scripts/addUsersToResend.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • packages/resend/src/contacts.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.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)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.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/scripts/addUsersToResend.ts
  • packages/resend/src/contacts.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
  • apps/web/utils/email/microsoft.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/microsoft.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/microsoft.ts
🧠 Learnings (5)
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-07-20T09:00:41.968Z
Learning: Applies to apps/web/app/api/**/*.{ts,js} : Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
📚 Learning: 2025-07-08T13:14:03.250Z
Learnt from: elie222
PR: elie222/inbox-zero#537
File: apps/web/app/api/user/stats/newsletters/route.ts:235-239
Timestamp: 2025-07-08T13:14:03.250Z
Learning: The user prefers proper TypeScript error type checking (using `error instanceof Error`) over type assertions with `as any` for better type safety and code quality.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
📚 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/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx
📚 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/hooks/**/*.{ts,tsx} : Call `mutate()` after successful mutations to refresh data

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
📚 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/hooks/**/*.ts : Call `mutate()` after successful mutations to refresh SWR data on the client.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx
🧬 Code graph analysis (6)
apps/web/scripts/addUsersToResend.ts (1)
packages/resend/src/contacts.ts (1)
  • createContact (3-14)
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (1)
apps/web/app/api/user/me/route.ts (1)
  • UserResponse (7-7)
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts (1)
apps/web/app/api/user/me/route.ts (1)
  • UserResponse (7-7)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
apps/web/utils/actions/rule.validation.ts (1)
  • CreateRuleBody (159-159)
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (1)
apps/web/app/api/user/me/route.ts (1)
  • UserResponse (7-7)
apps/web/utils/email/microsoft.ts (2)
apps/web/utils/email/google.ts (3)
  • draftEmail (256-276)
  • createFilter (415-421)
  • createAutoArchiveFilter (423-432)
apps/web/utils/outlook/filter.ts (2)
  • createFilter (14-51)
  • createAutoArchiveFilter (53-91)
⏰ 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: Jit Security
🔇 Additional comments (8)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)

1038-1038: Typed form errors look good

FieldErrors<CreateRuleBody> is the right shape for RHF + Zod.


1380-1383: Good UX fallback for delay error

Providing "Invalid delay value" when the message is missing improves clarity.

apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx (1)

7-11: LGTM: stricter Error typing

Nicer than any, aligns with the “no explicit any” effort.

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

1-1: Importing Message type is fine

Type import aligns with Graph usage.


406-409: Robust label fallbacks

Using label.id as a fallback prevents empty names/ids from leaking.

Also applies to: 418-421


298-302: Remove unnecessary shape verification
The draftEmail helper returns a Graph Message object with id at the top level, so accessing result.id is correct.


439-453: No change needed: Outlook addLabelIds uses category names
Outlook’s assignCategories field returns category NAMES and the Outlook adapter passes these directly into its createFilter call—which expects category names—so the current mapping is correct.

apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (1)

18-19: LGTM: consistent refetchPremium/mutate typings across hooks

The new signatures are consistent and improve safety.

Also applies to: 28-29, 57-58, 116-119, 170-172, 204-207, 290-292, 341-343, 371-372, 471-472, 570-571, 604-605

Comment on lines +136 to 137
// biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: (data?: any, options?: any) => void;
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

Eliminate noExplicitAny on mutate by using the hook’s actual type

Use the mutate type from useRule instead of any.

-  // biome-ignore lint/suspicious/noExplicitAny: lazy
-  mutate?: (data?: any, options?: any) => void;
+  mutate?: ReturnType<typeof useRule>["mutate"];
📝 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
// biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: (data?: any, options?: any) => void;
- // biome-ignore lint/suspicious/noExplicitAny: lazy
mutate?: ReturnType<typeof useRule>["mutate"];
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx around lines
136-137, the prop type for mutate is declared as any with a biome-ignore
comment; replace this with the actual mutate type exported/returned by the
useRule hook (import or derive the type from useRule's return type), update the
mutate prop signature to use that specific type (e.g., mutate?:
ReturnType<typeof useRule>["mutate"] or an exported type alias from useRule),
and remove the biome-ignore comment so noExplicitAny is no longer needed.

Comment on lines +15 to 17
const result = await createContact({ email: user.email });
const error = result && "error" in result ? result.error : undefined;
if (error) console.error(error);
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

Strengthen the runtime type guard; also replace console with scoped logger and use shared Prisma util

  • The "error" in result check is a good move. Add an typeof result === "object" guard to avoid using in on non-objects.
  • Per guidelines, avoid console in backend TS; use createScopedLogger.
  • Prefer the shared Prisma singleton (import prisma from "@/utils/prisma";) over instantiating new PrismaClient().
  • Optionally await disconnect in finally.

Apply these diffs:

-import { PrismaClient } from "@prisma/client";
+import prisma from "@/utils/prisma";
+import { createScopedLogger } from "@/utils/logger";

-const prisma = new PrismaClient();
+const log = createScopedLogger("scripts/addUsersToResend");

@@
-        console.log("Adding user", user.email);
+        log.info("Adding user", { email: user.email });
-        const result = await createContact({ email: user.email });
-        const error = result && "error" in result ? result.error : undefined;
-        if (error) console.error(error);
+        const result = await createContact({ email: user.email });
+        const error =
+          result && typeof result === "object" && "error" in result
+            ? // narrow for TS; adjust the shape if you type `createContact`
+              (result as { error?: unknown }).error
+            : undefined;
+        if (error) log.error("Resend createContact error", { email: user.email, error });

@@
-    } catch (error) {
-      console.error("Error creating contact for user: ", user.email, error);
+    } catch (error) {
+      log.error("Error creating contact for user", { email: user.email, error });
     }
@@
-main().finally(() => {
-  prisma.$disconnect();
-});
+main().finally(async () => {
+  await prisma.$disconnect();
+});

If createContact is updated to always throw on failures (as suggested in contacts.ts), you can simplify to:

await createContact({ email: user.email });
// only catch/LOG exceptions; no need to inspect `result.error`
🤖 Prompt for AI Agents
In apps/web/scripts/addUsersToResend.ts around lines 15 to 17, strengthen the
runtime guard and replace console and Prisma usage: ensure you check typeof
result === "object" before using "error" in result (or, if createContact now
throws on failure, simplify to just await createContact(...) inside a
try/catch), replace console.error with a createScopedLogger instance (import and
use createScopedLogger for this script), import the shared Prisma singleton
(import prisma from "@/utils/prisma") instead of instantiating new
PrismaClient(), and if you keep a manually-managed client ensure you await
client.$disconnect() in a finally block.

@elie222 elie222 merged commit feb2cb1 into main Sep 1, 2025
7 of 8 checks passed
@elie222 elie222 deleted the feat/better-microsoft-client-typing branch September 1, 2025 12:49
@coderabbitai coderabbitai bot mentioned this pull request Dec 14, 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