Skip to content

Digest improvements#704

Merged
elie222 merged 21 commits intoelie222:mainfrom
edulelis:digest-emails-v15
Aug 28, 2025
Merged

Digest improvements#704
elie222 merged 21 commits intoelie222:mainfrom
edulelis:digest-emails-v15

Conversation

@edulelis
Copy link
Collaborator

@edulelis edulelis commented Aug 20, 2025

image

Summary by CodeRabbit

  • New Features

    • Dynamic, category-driven digest subjects and a “Customize what you receive” footer link.
  • Improvements

    • Revamped digest layout with color-accented category cards, clearer From lines, and bulletized content.
    • Summaries simplified to a single content string and now include the user’s name, context, and category-aware rules.
    • Broader email content extraction and propagation of message snippets for actions.
  • Bug Fixes

    • Digest sending skipped when no rules executed.
  • Tests

    • Standardized fixtures and greatly expanded summary test coverage.

@vercel
Copy link

vercel bot commented Aug 20, 2025

@edulelis is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Walkthrough

Consolidates AI digest summaries to a single-string content, includes account name in account fetches and AI prompts, widens accepted email input shapes and propagates snippet/emailAccountId, refactors digest email template and dynamic subject generation, and adds an early-return guard when no executed rules exist.

Changes

Cohort / File(s) Summary of changes
AI summarization logic & tests
apps/web/utils/ai/digest/summarize-email-for-digest.ts, apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
Schema simplified: content is now a string (no structured/unstructured union). Prompt now includes a <user> block with name. aiSummarizeEmailForDigest expects emailAccount to include name. Tests refactored with factories and updated expectations.
AI digest API route
apps/web/app/api/ai/digest/route.ts
Call-site formatting adjusted and skip condition changed from if (!summary) to if (!summary?.content) to require non-empty content.
User getter: include name
apps/web/utils/user/get.ts
getEmailAccountWithAi now selects and returns name (return type `EmailAccountWithAI & { name: string
Digest queue & input typing
apps/web/utils/digest/index.ts, apps/web/utils/ai/types.ts, apps/web/utils/scheduled-actions/executor.ts
enqueueDigestItem widened to accept `ParsedMessage
Resend route & sender integration
apps/web/app/api/resend/digest/route.ts, packages/resend/src/send.tsx
Early-return guard added when no executed rules; emailAccountId added to emailProps; sendDigestEmail subject now computed via exported generateDigestSubject(emailProps) instead of a fixed string.
Digest email template & API
packages/resend/emails/digest.tsx
Large refactor: DigestItem.contentstring; introduced NormalizedCategoryData and normalization flow; DigestEmailProps broadened (adds emailAccountId); added exported generateDigestSubject; rendering moved to per-category cards and bulletized multi-line content; footer link uses emailAccountId.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as /api/ai/digest
  participant UserGet as getEmailAccountWithAi
  participant Summ as aiSummarizeEmailForDigest
  participant Queue as enqueueDigestItem

  Client->>API: POST { email, category, emailAccountId }
  API->>UserGet: fetch account (id, name, AI config)
  UserGet-->>API: EmailAccountWithAI & { name }
  API->>Summ: summarize(email, category, account+name)
  Summ-->>API: { content: string } | null
  alt content present
    API->>Queue: enqueueDigestItem({ email, emailAccountId, content })
    Queue-->>API: ack
    API-->>Client: 200
  else missing/empty content
    API-->>Client: 204
  end
Loading
sequenceDiagram
  autonumber
  actor Scheduler
  participant ResendRoute as /api/resend/digest
  participant Sender as sendDigestEmail
  participant Subject as generateDigestSubject
  participant Template as DigestEmailTemplate

  Scheduler->>ResendRoute: build emailProps (+emailAccountId, data)
  ResendRoute->>Sender: sendDigestEmail(emailProps)
  Sender->>Subject: generateDigestSubject(emailProps)
  Subject-->>Sender: subject string
  Sender->>Template: render HTML (normalized props)
  Template-->>Sender: HTML
  Sender-->>ResendRoute: sent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hopped through lines with nimble paws,
Names and snippets stitched the cause.
Cards and subjects tidy, neat,
Digests hum with rhythmic beat.
A carrot nod — the job's complete. 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit 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 @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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: 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/app/api/ai/digest/route.ts (1)

133-134: Ensure raw string summaries are stored and consumed correctly

The digest generator now returns a plain string, so storing it with JSON.stringify will persist extra quotes and escaped newlines—and the existing resend endpoint uses JSON.parse(item.content) and skips on parse errors. To avoid breaking backwards compatibility and to handle both legacy (JSON-serialized) and new (raw) content, you need to update both the write and read paths:

• In apps/web/app/api/ai/digest/route.ts (around lines 133–134), stop stringifying raw summaries:

-    const contentString = JSON.stringify(content);
+    // content is already a plain string summary
+    const contentString = content;

• In apps/web/app/api/resend/digest/route.ts (around line 192), adjust parsing so that raw strings fall back gracefully instead of skipping the item:

-        let parsedContent: unknown;
-        try {
-          parsedContent = JSON.parse(item.content);
-        } catch (error) {
-          logger.warn("Failed to parse digest item content, skipping item", {
-            content: item.content,
-            error,
-          });
-          continue;
-        }
+        let parsedContent: unknown;
+        try {
+          // Try parsing legacy JSON-serialized content
+          parsedContent = JSON.parse(item.content);
+        } catch {
+          // For plain-string summaries, use the raw content directly
+          parsedContent = item.content;
+        }

These changes ensure:

  • New summaries are stored cleanly as raw strings
  • Old entries (where content is a JSON-serialized string or object) continue to be parsed correctly
  • No items are inadvertently dropped due to parse errors
🧹 Nitpick comments (4)
apps/web/utils/user/get.ts (1)

63-90: Good addition; consider a dedicated type alias to make the return type self-documenting and preserve all user fields.

The intersection works, but declaring the merged shape once reduces repetition and avoids accidental narrowing if EmailAccountWithAI["user"] grows later.

Apply within this hunk:

-}): Promise<(EmailAccountWithAI & { user: { name: string | null } }) | null> {
+}): Promise<EmailAccountWithAIAndName | null> {

Add this near the existing imports (outside this hunk):

export type EmailAccountWithAIAndName =
  EmailAccountWithAI & { user: EmailAccountWithAI["user"] & { name: string | null } };
apps/web/app/api/ai/digest/route.ts (1)

160-173: Use the child logger returned by .with() so your structured context is not dropped.

.with() typically returns a new logger; calling it without assigning means subsequent logs don’t carry emailAccountId and messageId.

 export const POST = withError(
   verifySignatureAppRouter(async (request: Request) => {
-    const logger = createScopedLogger("digest");
+    const baseLogger = createScopedLogger("digest");
@@
-      logger.with({ emailAccountId, messageId: message.id });
+      const logger = baseLogger.with({ emailAccountId, messageId: message.id });
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

67-75: Coalesce nulls for about/name to avoid the literal “null” entering prompts; optionally escape XML.

If about or user.name is null, the current template injects the string “null”, which can degrade LLM output. Also, escaping basic XML characters reduces prompt-structure breakage on unusual input.

-  const prompt = `
+  const userAbout = emailAccount.about ?? "";
+  const userName = emailAccount.user.name ?? "";
+
+  const prompt = `
 <email>
   <content>${stringifyEmailSimple(userMessageForPrompt)}</content>
   <category>${ruleName}</category>
 </email>
 
 <user>
-  <about>${emailAccount.about}</about>
-  <name>${emailAccount.user.name}</name>
+  <about>${userAbout}</about>
+  <name>${userName}</name>
 </user>`;

Optional (outside this hunk): add a tiny escaper and wrap values if you want stricter safety.

const esc = (s: string) =>
  s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
packages/resend/emails/digest.tsx (1)

273-301: Avoid using array index as a React key in lists.

Keys should be stable across reorders; use content-derived keys for better reconciliation in email previews.

-              {lines.map((line, index) => (
-                <li key={index} className="text-[14px] text-gray-800 mb-[1px]">
+              {lines.map((line) => (
+                <li key={line.trim()} className="text-[14px] text-gray-800 mb-[1px]">
                   {line.trim()}
                 </li>
               ))}

If duplicates are possible, incorporate a short hash suffix instead of the index.

📜 Review details

Configuration used: .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 1995501 and 1199855.

📒 Files selected for processing (4)
  • apps/web/app/api/ai/digest/route.ts (2 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • apps/web/utils/user/get.ts (1 hunks)
  • packages/resend/emails/digest.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
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/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/**/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
!{.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/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.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/app/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/app/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
**/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
**/*.{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/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
!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/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
apps/web/utils/**

📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
apps/web/utils/{ai,llms}/**/*.ts

📄 CodeRabbit Inference Engine (.cursor/rules/llm.mdc)

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.tsx
🧠 Learnings (4)
📚 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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/app/api/ai/digest/route.ts
  • apps/web/utils/user/get.ts
📚 Learning: 2025-07-18T17:27:46.389Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-07-18T17:27:46.389Z
Learning: Applies to **/api/**/route.ts : Use `withEmailAccount` middleware for API routes that operate on a specific email account (i.e., use or require `emailAccountId`).

Applied to files:

  • apps/web/app/api/ai/digest/route.ts
📚 Learning: 2025-07-18T15:05:26.713Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-07-18T15:05:26.713Z
Learning: Applies to app/api/**/route.ts : Always wrap the handler with `withAuth` or `withEmailAccount` for consistent error handling and authentication in GET API routes.

Applied to files:

  • apps/web/app/api/ai/digest/route.ts
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
🧬 Code Graph Analysis (3)
apps/web/app/api/ai/digest/route.ts (1)
apps/web/utils/user/get.ts (1)
  • getEmailAccountWithAiAndName (63-90)
apps/web/utils/user/get.ts (1)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
⏰ 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 (3)
apps/web/app/api/ai/digest/route.ts (1)

9-9: Import swap to include user.name looks correct.

Usage aligns with downstream summarizer changes expecting emailAccount.user.name. Nothing else needed here.

apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

26-27: Param type update is aligned with new data needs.

Accepting EmailAccountWithAI & { user: { name: string | null } } is consistent with the new getter. No issues here.

packages/resend/emails/digest.tsx (1)

316-347: UI restructure for category sections reads well and matches the new content model.

The new header, count badge, and left-accented cards improve scannability. Rendering via a single renderEmailContent path aligns with the summarizer’s string output.

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

🧹 Nitpick comments (12)
packages/resend/package.json (2)

12-12: Pin versions for reproducible builds + confirm ESM interop for pluralize.

  • All other deps in this package are pinned; using carets here is inconsistent and can cause non-deterministic builds.
  • Also verify esModuleInterop is enabled; otherwise the default import of pluralize will fail in TS.

Apply this diff to pin versions:

-    "pluralize": "^8.0.0",
+    "pluralize": "8.0.0",
-    "@types/pluralize": "^0.0.33",
+    "@types/pluralize": "0.0.33",

If esModuleInterop is disabled for this package, switch to the compatibility import:

- import pluralize from "pluralize";
+ import pluralize = require("pluralize");

Also applies to: 20-20


12-12: Consider consistency: one pluralization strategy across the monorepo.

There’s an existing helper at apps/web/utils/string.ts named pluralize. Using the library here is fine (it handles irregular forms), but we should standardize on one approach (either migrate that helper to a shared package or deprecate it) to avoid drift.

apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (2)

54-54: Remove console.debug statements (repo guideline: “Don’t use console”).

These will fail linting and add noise to CI logs. If you need diagnostics locally, consider temporary vi.spyOn(console, "debug") within a single test or remove entirely.

Apply this diff to remove them:

-    console.debug("Generated content:\n", result);

Also applies to: 85-85, 110-110, 149-149


31-35: Add a complementary non-AI test path so this suite isn’t entirely skipped when RUN_AI_TESTS !== "true".

Current describe.runIf(isAiTest) skips all tests in normal runs. Add a second block that runs with mocked LLM or exercises non-LLM branches (e.g., null/empty message paths) to keep baseline coverage.

Example addition (outside current hunk):

describe.runIf(!isAiTest)("aiSummarizeEmailForDigest (non-AI path)", () => {
  test("returns null for null message without calling LLM", async () => {
    const emailAccount = createTestEmailAccount();
    const result = await aiSummarizeEmailForDigest({
      ruleName: "other",
      emailAccount,
      messageToSummarize: null as any,
    });
    expect(result).toBeNull();
  });
});
packages/resend/emails/digest.tsx (8)

14-14: Pluralize import: ensure TS interop matches your tsconfig.

If this package’s tsconfig doesn’t have esModuleInterop: true, the default import will fail; use import pluralize = require("pluralize") instead. Otherwise this is fine.


73-86: Index signature includes undefined twice; streamline the union.

This is harmless but noisy.

Apply this diff:

   [key: string]:
-    | NormalizedCategoryData
-    | DigestItem[]
-    | undefined
-    | string
-    | Date
-    | Record<string, string>
-    | undefined;
+    | NormalizedCategoryData
+    | DigestItem[]
+    | string
+    | Date
+    | Record<string, string>
+    | undefined;

106-141: Normalization: compute and carry total unique senders; current “and more” uses item count.

Showing “and more” should reflect extra senders beyond the top 5, not total item count. Capture uniqueSenderCount during normalization.

Proposed changes:

-type NormalizedCategoryData = {
-  count: number;
-  senders: string[];
-  items: DigestItem[];
-};
+type NormalizedCategoryData = {
+  count: number;
+  senders: string[]; // top N (e.g., 5)
+  items: DigestItem[];
+  uniqueSenderCount?: number; // total unique senders across items
+};
   if (Array.isArray(data)) {
     const items = data;
-    const senders = Array.from(new Set(items.map((item) => item.from))).slice(
-      0,
-      5,
-    );
+    const allUniqueSenders = Array.from(new Set(items.map((item) => item.from)));
+    const senders = allUniqueSenders.slice(0, 5);
     return {
       count: items.length,
-      senders,
-      items,
+      senders,
+      items,
+      uniqueSenderCount: allUniqueSenders.length,
     };

153-181: Don’t use array index as React key in lists (repo guideline).

Keys based on indices can cause reconciliation issues; use stable content-based keys.

Apply this diff:

-              {lines.map((line, index) => (
-                <li key={index} className="text-[14px] text-gray-800 mb-[1px]">
-                  {line.trim()}
-                </li>
-              ))}
+              {lines.map((line) => {
+                const k = line.trim();
+                return (
+                  <li key={k} className="text-[14px] text-gray-800 mb-[1px]">
+                    {k}
+                  </li>
+                );
+              })}

If duplicate lines are possible, introduce a small helper to derive a stable key (e.g., ${line.length}:${k.slice(0,32)}) and reuse it.


200-218: “and more” should be based on unique senders, not item count.

Leverage the proposed uniqueSenderCount to signal truncated sender list accurately.

Apply this diff:

-                  {categoryData.count > 5 && " and more"}
+                  {(categoryData.uniqueSenderCount ?? categoryData.senders.length) >
+                    categoryData.senders.length && " and more"}

Also applies to: 262-278


225-251: Don’t use array index as key for email items (repo guideline).

Prefer a stable composite key (from + subject). If there’s a risk of duplicates, append a short hash of content.

Apply this diff:

-              {categoryData.items.map((item, index) => (
-                <div key={index}>
+              {categoryData.items.map((item) => (
+                <div key={`${item.from}::${item.subject}`}>
                   <div className="p-[20px]">
                     {/* Email Header */}

If collisions are a concern, add a helper outside this hunk:

// helper near top-level
const itemKey = (i: DigestItem) =>
  `${i.from}::${i.subject}::${(i.content ?? "").slice(0, 24)}`;

and then use key={itemKey(item)}.


210-218: Minor string assembly simplification.

You can simplify the sender list display using Array.join(", ").

Apply this diff:

-                  {categoryData.senders.map((sender, index) => {
-                    if (index === 0) {
-                      return sender;
-                    } else {
-                      return `, ${sender}`;
-                    }
-                  })}
+                  {categoryData.senders.join(", ")}

Also applies to: 270-278


316-330: Avoid double normalization per category.

normalizeCategoryData is called once for hasItems and again here. Cache results in a Map<string, NormalizedCategoryData> from the first pass to avoid recomputation and ensure consistent sender trimming.

📜 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 1199855 and e749e94.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (5 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • packages/resend/emails/digest.tsx (5 hunks)
  • packages/resend/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧰 Additional context used
📓 Path-based instructions (15)
!{.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:

  • packages/resend/package.json
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
!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:

  • packages/resend/package.json
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
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/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
**/*.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/ai/digest/summarize-email-for-digest.test.ts
**/*.test.{ts,js,tsx,jsx}

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

**/*.test.{ts,js,tsx,jsx}: Tests are colocated next to the tested file (e.g., dir/format.ts and dir/format.test.ts)
Use vi.mock("server-only", () => ({})); to mock the server-only module in tests
Mock @/utils/prisma in tests using vi.mock("@/utils/prisma") and use the provided prisma mock
Mock external dependencies in tests
Clean up mocks between tests
Do not mock the Logger

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
**/*.{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/ai/digest/summarize-email-for-digest.test.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.tsx
🧠 Learnings (3)
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths in LLM-related tests

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • packages/resend/emails/digest.tsx
🧬 Code graph analysis (2)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (1)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
packages/resend/emails/digest.tsx (1)
apps/web/utils/string.ts (1)
  • pluralize (40-46)
⏰ 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 (4)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (2)

13-29: Good factory extraction for test account shape.

This reduces duplication and centralizes the new user.name + account.provider additions.


37-37: LGTM: using the factory in each test reduces duplication and drift.

Also applies to: 68-68, 94-94, 120-120, 132-132

packages/resend/emails/digest.tsx (2)

190-190: Color fallback is solid.

Nice use of a default “gray” when the category is unmapped.


624-634: Nice UX addition.

“Customize what you receive” link in the footer is a good touch for prefs discoverability.

@elie222
Copy link
Owner

elie222 commented Aug 25, 2025

Remove WIP when done

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 (1)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

94-99: Normalize “empty or literal 'null'” to null to keep callers simple.

Even with the new empty-string semantics, some models occasionally output "null". Normalizing here reduces downstream conditionals and preserves prior behavior.

-    return aiResponse.object;
+    const result = aiResponse.object;
+    const normalized = (result.content ?? "").trim().toLowerCase();
+    if (!normalized || normalized === "null") {
+      return null;
+    }
+    return result;
🧹 Nitpick comments (14)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

76-76: Include context in logs and leverage scoped logger fields.

Add ruleName and message identifiers to aid triage, using the logger’s with() to enrich subsequent logs.

-  logger.info("Summarizing email for digest");
+  const log = logger.with({
+    ruleName,
+    subject: userMessageForPrompt.subject,
+    from: userMessageForPrompt.from,
+  });
+  log.info("Summarizing email for digest");
apps/web/app/api/ai/digest/route.ts (1)

166-167: logger.with() result is unused; fields won’t be attached to subsequent logs.

Capture the returned logger and use it (as shown in the diff above) to ensure contextual fields are emitted.

packages/resend/emails/digest.tsx (2)

211-216: Avoid using array index as React key.

Use a stable key (e.g., a hash of the line) to prevent reconciliation glitches. Low impact in emails but easy to harden.

-            {lines.map((line: string, index: number) => (
-              <li key={index} className="text-[14px] text-gray-800 mb-[1px]">
+            {lines.map((line: string) => (
+              <li key={line} className="text-[14px] text-gray-800 mb-[1px]">
                 {line.trim()}
               </li>
             ))}

301-314: “and more” condition should reflect sender list, not item count.

We slice unique senders to 5; append “and more” only if there were >5 unique senders.

-              {categoryData.count > 5 && " and more"}
+              {categoryData.senders.length === 5 && categoryData.count > 5 && " and more"}
packages/resend/src/send.tsx (1)

29-36: Replace console logging with a scoped logger for consistency and testability.

Project-wide guidance discourages console.*; consider a lightweight logger in this package or plumb one in.

-  if (!resend) {
-    console.log(
-      "Resend is not configured. You need to add a RESEND_API_KEY in your .env file for emails to work.",
-    );
+  if (!resend) {
+    // TODO: inject a logger into this package and use it here
+    // logger.warn("Resend is not configured. Set RESEND_API_KEY to enable emails.");
     return Promise.resolve();
   }
@@
-  if (result.error) {
-    console.error("Error sending email", result.error);
+  if (result.error) {
+    // logger.error("Error sending email", { error: result.error });
     throw new Error(`Error sending email: ${result.error.message}`);
   }

Also applies to: 54-57

apps/web/utils/upstash/signature.ts (4)

1-1: Mark this utility as server-only to prevent accidental client bundling

This file imports node:crypto and uses Buffer. Guard it with server-only to avoid accidental client imports/bundling.

Apply:

+import "server-only";
 import crypto from "node:crypto";
 import { env } from "@/env";

4-11: Prefer native "base64url" over manual replacements

Node supports "base64url" encoding; it’s simpler and less error-prone than regex replacements.

-function base64UrlEncode(input: Buffer | string) {
-  const buff = Buffer.isBuffer(input) ? input : Buffer.from(input);
-  return buff
-    .toString("base64")
-    .replace(/=/g, "")
-    .replace(/\+/g, "-")
-    .replace(/\//g, "_");
-}
+function base64UrlEncode(input: Buffer | string) {
+  const buff = Buffer.isBuffer(input) ? input : Buffer.from(input);
+  return buff.toString("base64url");
+}

27-31: Ensure deterministic hashing when callers pass a pre-serialized JSON string

If a caller ever passes a string (already-serialized JSON), the current code will wrap it in quotes on JSON.stringify, changing the signed payload. Handle strings explicitly.

-  const bodyString = JSON.stringify(requestBody ?? {});
+  const bodyString =
+    typeof requestBody === "string" ? requestBody : JSON.stringify(requestBody ?? {});
   const bodyHash = crypto.createHash("sha256").update(bodyString).digest();

53-81: Simplify header normalization, avoid clobbering Content-Type, and fall back to CURRENT signing key

  • new Headers already accepts HeadersInit, no need for manual iterator branching.
  • Only set Content-Type if missing.
  • If NEXT key isn’t set, fall back to CURRENT to keep local dev working with the verifier.
 export function buildUpstashRequestHeaders({
   baseHeaders,
   endpointUrl,
   requestBody,
 }: {
-  baseHeaders?: HeadersInit | IterableIterator<[string, string]>;
+  baseHeaders?: HeadersInit;
   endpointUrl: string;
   requestBody: unknown;
 }): HeadersInit {
-  const normalizedHeaders =
-    baseHeaders && Symbol.iterator in baseHeaders
-      ? Array.from(baseHeaders as IterableIterator<[string, string]>)
-      : baseHeaders;
-
-  const headers = new Headers(normalizedHeaders);
-  headers.set("Content-Type", "application/json");
+  const headers = new Headers(baseHeaders);
+  if (!headers.has("content-type")) {
+    headers.set("Content-Type", "application/json");
+  }
 
-  const signingKey = env.QSTASH_NEXT_SIGNING_KEY;
+  const signingKey = env.QSTASH_NEXT_SIGNING_KEY ?? env.QSTASH_CURRENT_SIGNING_KEY;
   if (signingKey) {
     const signature = createUpstashSignature({
       endpointUrl,
       requestBody,
       signingKey,
     });
     headers.set("Upstash-Signature", signature);
   }
 
   return headers;
 }
apps/web/utils/upstash/index.ts (5)

1-6: Don’t shadow the global DOM HeadersInit type; remove it from the @upstash/qstash import

@upstash/qstash’s HeadersInit may diverge from the global fetch type and can cause confusion when passing to fetch/buildUpstashRequestHeaders. Use the global HeadersInit instead.

-import { Client, type FlowControl, type HeadersInit } from "@upstash/qstash";
+import { Client, type FlowControl } from "@upstash/qstash";

10-12: Intent check: disabling the QStash client in development

This forces the fallback path in dev even if a token exists. If you want the option to test the real QStash client locally, consider a feature flag (e.g., QSTASH_ENABLE_IN_DEV=true).

Proposed tweak:

-function getQstashClient() {
-  if (!env.QSTASH_TOKEN || env.NODE_ENV === "development") return null;
+function getQstashClient() {
+  if (
+    !env.QSTASH_TOKEN ||
+    (env.NODE_ENV === "development" && !env.QSTASH_ENABLE_IN_DEV)
+  )
+    return null;
   return new Client({ token: env.QSTASH_TOKEN });
 }

51-54: Consider bounded parallelism in fallback bulk publish

Sequential loop can be slow on large batches. Optional: use Promise.allSettled with a small concurrency limit (e.g., p-limit 5) for better throughput in dev while avoiding spikes.


56-78: Queue publish fallback ignores flow-control semantics

publishToQstashQueue’s fallback just POSTs to the URL and doesn’t emulate parallelism/queueing. That’s fine for dev, but call it out in the function JSDoc (or log once) to avoid confusion.


34-35: Normalize return types in Upstash utils (optional)

Currently, in apps/web/utils/upstash/index.ts, the following functions return a Qstash client response when a real client is available but void when falling back to fetch:

  • publishToQstash
  • bulkPublishToQstash
  • publishToQstashQueue

Although all existing call sites simply await these calls without using the returned value, this mismatch may lead to confusion or bugs if anyone later relies on the response shape. To improve type safety and consistency, consider one of the following refactoring options:

• Always return void by discarding the client’s response:

// Example for publishToQstash
if (client) {
  await client.publishJSON({});
  return;
}
await fallbackPublishToQstash(url, body);

• Normalize to a common response type by synthesizing a minimal stub in the fallback branch:

if (client) {
  return client.publishJSON({});
}
await fallbackPublishToQstash(url, body);
return { requestId: null, scheduledAt: new Date().toISOString() }; // or other minimal fields

In either case, update the function signatures to explicitly declare the unified return type (e.g. Promise<void> or Promise<PublishResponse>), ensuring callers see a single, predictable API.

📜 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 e749e94 and 4340da4.

📒 Files selected for processing (7)
  • apps/web/app/api/ai/digest/route.ts (2 hunks)
  • apps/web/app/api/resend/digest/route.ts (1 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • apps/web/utils/upstash/index.ts (2 hunks)
  • apps/web/utils/upstash/signature.ts (1 hunks)
  • packages/resend/emails/digest.tsx (4 hunks)
  • packages/resend/src/send.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
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/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.ts
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/resend/digest/route.ts
  • apps/web/app/api/ai/digest/route.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/resend/digest/route.ts
  • apps/web/app/api/ai/digest/route.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/app/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • packages/resend/src/send.tsx
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.ts
  • packages/resend/emails/digest.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/app/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.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/app/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • packages/resend/src/send.tsx
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.ts
  • packages/resend/emails/digest.tsx
**/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/resend/digest/route.ts
  • 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/resend/digest/route.ts
  • apps/web/app/api/ai/digest/route.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/app/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • packages/resend/src/send.tsx
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.ts
  • packages/resend/emails/digest.tsx
!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/api/resend/digest/route.ts
  • apps/web/utils/upstash/signature.ts
  • packages/resend/src/send.tsx
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/app/api/ai/digest/route.ts
  • packages/resend/emails/digest.tsx
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/upstash/signature.ts
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/upstash/signature.ts
  • apps/web/utils/upstash/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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:

  • packages/resend/src/send.tsx
  • packages/resend/emails/digest.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:

  • packages/resend/src/send.tsx
  • packages/resend/emails/digest.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:

  • packages/resend/src/send.tsx
  • packages/resend/emails/digest.tsx
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧠 Learnings (8)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/app/api/resend/digest/route.ts
  • packages/resend/src/send.tsx
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
📚 Learning: 2025-07-18T17:27:46.389Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-07-18T17:27:46.389Z
Learning: Applies to **/api/**/route.ts : Use `withEmailAccount` middleware for API routes that operate on a specific email account (i.e., use or require `emailAccountId`).

Applied to files:

  • apps/web/app/api/resend/digest/route.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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/app/api/resend/digest/route.ts
  • apps/web/app/api/ai/digest/route.ts
📚 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} : All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.

Applied to files:

  • apps/web/utils/upstash/signature.ts
  • apps/web/utils/upstash/index.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 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} : Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Applied to files:

  • apps/web/app/api/ai/digest/route.ts
📚 Learning: 2025-07-18T15:05:26.713Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-07-18T15:05:26.713Z
Learning: Applies to app/api/**/route.ts : Always wrap the handler with `withAuth` or `withEmailAccount` for consistent error handling and authentication in GET API routes.

Applied to files:

  • apps/web/app/api/ai/digest/route.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 `actionClient` when both authenticated user context and a specific `emailAccountId` are needed. The `emailAccountId` must be bound when calling the action from the client.

Applied to files:

  • apps/web/app/api/ai/digest/route.ts
🧬 Code graph analysis (4)
packages/resend/src/send.tsx (1)
packages/resend/emails/digest.tsx (1)
  • generateDigestSubject (94-130)
apps/web/utils/upstash/index.ts (2)
apps/web/env.ts (1)
  • env (16-225)
apps/web/utils/upstash/signature.ts (1)
  • buildUpstashRequestHeaders (53-81)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
apps/web/app/api/ai/digest/route.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-65)
apps/web/app/api/ai/digest/validation.ts (1)
  • digestBody (3-15)
apps/web/utils/user/get.ts (1)
  • getEmailAccountWithAiAndName (63-90)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)
  • aiSummarizeEmailForDigest (19-100)
⏰ 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 (6)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

11-13: Schema allows empty string — aligns with “skip on spam” rule.

Accepting an empty string for content is correct given the prompt’s “spam/promotional → empty string” instruction. No change needed.

packages/resend/emails/digest.tsx (2)

94-130: Subject generation looks good and defensive against empty digests.

Sorting, pluralization, and fallbacks read well. No changes suggested.


352-366: Category normalization is correctly centralized at render-time.

Nice separation via normalizeCategoryData; keeps DigestEmailProps flexible given the index signature constraint.

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

229-230: Passing emailAccountId through to emailProps is correct and required by the footer/settings link.

Matches the updated DigestEmailProps and avoid broken “Customize what you receive” links.

packages/resend/src/send.tsx (1)

6-9: Dynamic subject via generateDigestSubject is a solid upgrade.

Import and usage are correct; this keeps subjects relevant without duplicating logic.

Also applies to: 128-129

apps/web/utils/upstash/signature.ts (1)

32-41: No changes needed for QStash signature claims

After digging into the QStash Next.js integration:

  • The JWT payload key for the body digest is indeed “body,” which matches the Receiver.verify implementation’s expectations when it validates the HMAC over header.payload (the verifier reads the body claim directly) (github.com).
  • The sub claim is never checked by verifySignatureAppRouter (it only calls receiver.verify({ signature, body, … }) and ignores all other JWT claims) (github.com).

Thus, no further verification against QStash’s library is required: the code correctly uses “body” for the digest, and mismatched sub values won’t break signature validation. Feel free to resolve this 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 (2)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)

94-99: Normalize spam/irrelevant outputs to null to preserve caller semantics.

Prompt says spam should return an empty string. Callers typically treat “no digest item” as null. Normalize here.

-    return aiResponse.object;
+    const result = aiResponse.object;
+    const normalized = (result.content ?? "").trim().toLowerCase();
+    if (!normalized || normalized === "null") {
+      return null;
+    }
+    return result;

56-62: Clarify response format so validation is guaranteed to succeed.

The current “Return only the final result” can lead to plain-text outputs that don’t match the Zod object. Instruct the model to return a JSON object with a content field.

 **Formatting rules:**
 - Do not include HTML, markdown, or explanations.
-- Return only the final result.
-- For structured emails, return the bulleted list.
-- For unstructured emails, return the summarized text.
-- For spam, promotional, or irrelevant emails, return an empty string.
+- Response format: Return ONLY a JSON object matching this exact schema:
+- { "content": string }
+- For structured emails, place the bulleted list in the "content" field.
+- For unstructured emails, place the summarized text in the "content" field.
+- For spam, promotional, or irrelevant emails, set "content" to an empty string.
🧹 Nitpick comments (6)
apps/web/utils/user/get.ts (2)

76-82: Consider also selecting user.name in getEmailAccountWithAiAndTokens for parity (optional).

If any caller of the tokens variant later needs the name for prompts, you’ll avoid another round-trip. Safe, low-risk addition.

Apply this diff if you want parity:

       user: {
         select: {
           aiProvider: true,
           aiModel: true,
-          aiApiKey: true,
+          aiApiKey: true,
+          name: true,
         },
       },

96-103: Minor: tokens duplication vs account nesting (optional clean-up).

You return both account.{access_token,...} and a flattened tokens copy. That’s convenient but redundant. Consider returning only tokens (or only account) to reduce confusion.

apps/web/utils/ai/digest/summarize-email-for-digest.ts (4)

9-13: Schema simplification to a single string is fine; consider trimming.

To normalize whitespace from the model, you can add .transform((s) => s.trim()) so consumers don’t have to trim.

 export const schema = z.object({
   content: z
-    .string()
+    .string()
+    .transform((s) => s.trim())
     .describe("The content - either structured entries or summary text"),
 });

25-27: Redundant intersection type; EmailAccountWithAI already includes user.name.

Since EmailAccountWithAI now selects user.name, the & { user: { name: string | null } } is unnecessary and risks future drift.

-  emailAccount: EmailAccountWithAI & { user: { name: string | null } };
+  emailAccount: EmailAccountWithAI;

76-76: Add non-PII context to logs.

Include minimal context like category and provider; avoid message content.

-  logger.info("Summarizing email for digest");
+  logger.info("Summarizing email for digest", {
+    category: ruleName,
+    provider: emailAccount.account.provider,
+  });

87-93: Wrap AI calls with retry logic using withRetry

The withRetry helper is already defined (e.g. in apps/web/utils/ai/choose-rule/ai-choose-args.ts), so we can reuse it here to guard against transient LLM failures.

• File: apps/web/utils/ai/digest/summarize-email-for-digest.ts
• Location: around the generateObject call (lines 87–93)

Suggested diff:

-    const aiResponse = await generateObject({
-      ...modelOptions,
-      system,
-      prompt,
-      schema,
-    });
+    const aiResponse = await withRetry(() =>
+      generateObject({
+        ...modelOptions,
+        system,
+        prompt,
+        schema,
+      })
+    );
📜 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 4340da4 and 11a8bb7.

📒 Files selected for processing (4)
  • apps/web/app/api/ai/digest/route.ts (1 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • apps/web/utils/llms/types.ts (1 hunks)
  • apps/web/utils/user/get.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/ai/digest/route.ts
🧰 Additional context used
📓 Path-based instructions (9)
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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/llms/types.ts
  • apps/web/utils/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/llms/types.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧠 Learnings (2)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧬 Code graph analysis (1)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-30)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
🔇 Additional comments (2)
apps/web/utils/llms/types.ts (1)

21-22: Ensure null-safe handling for user.name in prompts

The Prisma schema defines User.name as nullable (String?), so EmailAccountWithAI['name'] is typed as string | null. Downstream code that incorporates this value into AI prompts should explicitly sanitize or default null values (e.g. user.name ?? "") to prevent the literal "null" from appearing.

• apps/web/prisma/schema.prisma: line 49 – name String?
• apps/web/utils/llms/types.ts: lines 21–22 – name: true; (results in string | null)
• apps/web/api/summarize-email-for-digest.ts – wrap user.name usage with a null-coalescing or fallback before inserting into the prompt.

apps/web/utils/user/get.ts (1)

48-53: Adding user.name to getEmailAccountWithAi aligns with the updated type.

This keeps the return type consistent with EmailAccountWithAI and unblocks name-aware prompts.

@edulelis edulelis changed the title WIP: Digest prompt changes and proposed mail template v2 Digest prompt changes and proposed mail template v2 Aug 25, 2025
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 (4)
apps/web/utils/user/get.ts (1)

63-90: Avoid duplication with getEmailAccountWithAi (same select + name).

This function largely duplicates getEmailAccountWithAi with only user.name added. Consider sharing a base select object or folding into a single helper that conditionally includes fields to prevent drift.

If you want, I can propose a small baseSelect refactor that both functions consume.

apps/web/utils/ai/digest/summarize-email-for-digest.ts (3)

56-63: Explicitly require a JSON object output to match the Zod schema.

The prompt currently permits plain text, which risks validation failures or brittle extraction. Add a “Response format” rule that instructs the model to return ONLY a JSON object matching { "content": string }.

Apply this diff:

 **Formatting rules:**
 - Do not include HTML, markdown, or explanations.
-- For structured emails, return the bulleted list.
-- For unstructured emails, return the summarized text.
-- For spam, promotional, or irrelevant emails, return an empty string.
+- Response format: Return ONLY a JSON object matching this exact schema:
+- { "content": string }
+- For structured emails, set "content" to the bulleted list (one item per line, "- Key: Value").
+- For unstructured emails, set "content" to the summarized text.
+- For spam, promotional, or irrelevant emails, set "content" to an empty string.
 
-Now, classify and summarize the following email:`;
+Now, classify and summarize the following email:`;

93-98: Normalize spam/irrelevant outputs (“”, “null”) to return null.

The route now checks summary?.content, which handles empty strings, but not the literal "null". Normalize here to prevent leaking "null" into emails and keep a single contract: null means “skip”.

Apply this diff:

-    return aiResponse.object;
+    const result = aiResponse.object;
+    const normalized = (result.content ?? "").trim().toLowerCase();
+    if (!normalized || normalized === "null") {
+      return null;
+    }
+    return result;

64-73: Sanitize nullable fields and bound prompt size; also guard XML-like tags with CDATA.

emailAccount.about and user.name can be null and currently render as the literal string "null". Also, very long email bodies can bloat prompts. Sanitize and truncate, and wrap interpolations in CDATA to avoid accidental tag breaks.

Apply this diff:

-  const prompt = `
-<email>
-  <email_content>${stringifyEmailSimple(userMessageForPrompt)}</email_content>
-  <email_category>${ruleName}</email_category>
-</email>
-
-<user>
-  <about>${emailAccount.about}</about>
-  <name>${emailAccount.user.name}</name>
-</user>`;
+  // Sanitize nullable fields and pre-truncate long inputs
+  const safeAbout = emailAccount.about ?? "";
+  const safeName = emailAccount.user.name ?? "";
+  const EMAIL_MAX_CHARS = 8000; // keep within model context; adjust as needed
+  const emailContentForPrompt = stringifyEmailSimple(userMessageForPrompt).slice(0, EMAIL_MAX_CHARS);
+
+  const prompt = `
+<email>
+  <email_content><![CDATA[${emailContentForPrompt}]]></email_content>
+  <email_category><![CDATA[${ruleName}]]></email_category>
+</email>
+
+<user>
+  <about><![CDATA[${safeAbout}]]></about>
+  <name><![CDATA[${safeName}]]></name>
+</user>`;
🧹 Nitpick comments (4)
apps/web/utils/user/get.ts (1)

63-90: Return shape matches Prisma select; consider a named type alias to reduce repetition.

The select adds user.name and the annotated return type intersects correctly with EmailAccountWithAI. To improve readability and reuse across call sites/tests, introduce a local alias and use it in the signature.

Apply this diff:

+export type EmailAccountWithAIAndName = EmailAccountWithAI & {
+  user: { name: string | null };
+};
+
 export async function getEmailAccountWithAiAndName({
   emailAccountId,
 }: {
   emailAccountId: string;
-}): Promise<(EmailAccountWithAI & { user: { name: string | null } }) | null> {
+}): Promise<EmailAccountWithAIAndName | null> {
apps/web/utils/ai/digest/summarize-email-for-digest.ts (3)

25-26: Type update is appropriate; consider a shared alias for EmailAccountWithAI + name.

The widened parameter type matches the new selector. For consistency with getEmailAccountWithAiAndName, prefer a named alias (e.g., EmailAccountWithAIAndName) to keep signatures concise.

Example (use the alias exported in utils/user/get.ts or define locally if needed):

// import type { EmailAccountWithAIAndName } from "@/utils/user/get";
emailAccount: EmailAccountWithAIAndName;

9-13: Consider trimming in schema to avoid whitespace-only content.

Add .trim() to the Zod string so whitespace-only responses normalize to "" and hit the spam/null guard.

Apply this diff:

 export const schema = z.object({
   content: z
-    .string()
+    .string()
+    .trim()
     .describe("The content - either structured entries or summary text"),
 });

77-92: Optional Improvement: Wrap LLM Call in withRetry and Enrich Logs

The project already provides a withRetry helper (in apps/web/utils/llms/index.ts) – import and use it to retry transient failures when calling generateObject. After the call, log a fixed message with ruleName and userEmail for better traceability (avoid logging prompt/content).

• File: apps/web/utils/ai/digest/summarize-email-for-digest.ts (around lines 77–92)
– Import withRetry from your LLM utils
– Wrap the generateObject call
– Add an info-level log with ruleName and userEmail

Example diff:

-import { createGenerateObject } from "@/utils/llms";
+import { createGenerateObject, withRetry } from "@/utils/llms";

 const generateObject = createGenerateObject({
   userEmail: emailAccount.email,
   label: "Summarize email",
   modelOptions,
 });

-const aiResponse = await generateObject({ ...modelOptions, system, prompt, schema });
+const aiResponse = await withRetry(
+  () => generateObject({ ...modelOptions, system, prompt, schema }),
+  { retries: 2, backoffMs: 300 }
+);
+logger.info("Summarize email for digest succeeded", {
+  ruleName: "summarize-email-for-digest",
+  userEmail: emailAccount.email,
+});
📜 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 11a8bb7 and ba16ff1.

📒 Files selected for processing (3)
  • apps/web/app/api/ai/digest/route.ts (3 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • apps/web/utils/user/get.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/api/ai/digest/route.ts
🧰 Additional context used
📓 Path-based instructions (9)
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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/user/get.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧠 Learnings (4)
📚 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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/utils/user/get.ts
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧬 Code graph analysis (2)
apps/web/utils/user/get.ts (1)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
⏰ 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 (2)
apps/web/utils/user/get.ts (2)

35-61: LGTM on correctness and typing.

The Prisma query, selected fields, and nullability are correct and align with downstream usage in the digest flow.

Also applies to: 63-90


63-90: Verify and update getEmailAccountWithAi call sites for the added name field
Since you’ve introduced getEmailAccountWithAiAndName (which now returns user.name), please ensure that any existing callers of the old getEmailAccountWithAi helper either switch to the new function or explicitly handle the name field.

Call sites still importing/using getEmailAccountWithAi:

  • apps/web/utils/user/get.ts:35
  • apps/web/utils/reply-tracker/generate-draft.ts
    • import at line 6
    • usage at line 37
  • apps/web/utils/actions/report.ts
    • import at line 18
    • usage at line 40
  • apps/web/utils/actions/ai-rule.ts
    • import at line 33
    • usages at lines 49, 121, 172, 600
  • apps/web/utils/actions/generate-reply.ts
    • import at line 8
    • usage at line 19
  • apps/web/utils/actions/rule.ts
    • import at line 47
    • usage at line 471
  • apps/web/app/api/chat/route.ts
    • import at line 4
    • usage at line 34
  • apps/web/app/api/ai/summarise/route.ts
    • import at line 7
    • usage at line 27
  • apps/web/app/api/ai/compose-autocomplete/route.ts
    • import at line 5
    • usage at line 10
  • apps/web/app/api/reply-tracker/process-previous/route.ts
    • import at line 8
    • usage at line 27

Please audit each of these locations to confirm they’re updated to use getEmailAccountWithAiAndName (or otherwise handle user.name).

@edulelis edulelis changed the title Digest prompt changes and proposed mail template v2 WIP: Digest prompt changes and proposed mail template v2 Aug 26, 2025
@edulelis edulelis changed the title WIP: Digest prompt changes and proposed mail template v2 Digest prompt changes and proposed mail template v2 Aug 26, 2025
@edulelis edulelis changed the title Digest prompt changes and proposed mail template v2 WIP: Digest prompt changes and proposed mail template v2 Aug 26, 2025
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 (2)
apps/web/utils/ai/types.ts (1)

4-14: Populate the required snippet field in the first EmailForAction instantiation

The EmailForAction literal in apps/web/utils/scheduled-actions/executor.ts around line 109 is missing the newly added snippet property. Without this, you’ll get TypeScript errors or an undefined value at runtime.

• File: apps/web/utils/scheduled-actions/executor.ts
Location: the block starting at line 109

const emailForAction: EmailForAction = {
  threadId: message.threadId,
  id: message.id,
  headers: message.headers,
  textPlain: message.textPlain || "",
  textHtml: message.textHtml || "",
+ snippet: message.snippet || "",
  attachments: message.attachments || [],
  internalDate: message.internalDate,
};

The second instantiation of EmailForAction (around line 177) already includes snippet. Please update the first one as shown to ensure consistency and avoid runtime issues.

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

177-186: emailMessage.snippet may be undefined — populate it earlier

You read snippet here, but validateEmailState never sets it, risking undefined content and breaking downstream logic relying on emailToContent.

Patch validateEmailState to include snippet:

     const emailForAction: EmailForAction = {
       threadId: message.threadId,
       id: message.id,
       headers: message.headers,
       textPlain: message.textPlain || "",
       textHtml: message.textHtml || "",
+      snippet: message.snippet || "",
       attachments: message.attachments || [],
       internalDate: message.internalDate,
     };
♻️ Duplicate comments (3)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (3)

56-62: Require JSON object output to match the Zod schema
Prevents plain-text responses that fail validation. (Previously raised.)

 **Formatting rules:**
 - Do not include HTML, markdown, or explanations.
+- Response format: Return ONLY a JSON object matching this exact schema: { "content": string }.
+- Output that JSON object and nothing else (no prose or extra text).
 - For structured emails, return the bulleted list.
 - For unstructured emails, return the summarized text.
 - For spam, promotional, or irrelevant emails, return an empty string.
 
 Now, classify and summarize the following email:`;

Follow-up (normalization at runtime, optional but safer if upstream expects nulls for spam):

// After receiving aiResponse
const content = aiResponse.object.content.trim();
if (!content) return null;
return { content };

65-68: Truncate/sanitize email content before embedding into the prompt
Avoids massive prompts and keeps within context limits. (Previously raised.)

-  <email_content>${stringifyEmailSimple(userMessageForPrompt)}</email_content>
+  <email_content>${emailContentForPrompt}</email_content>

Add above the prompt (outside this hunk):

const EMAIL_MAX_CHARS = 8000;
const emailContentForPrompt = stringifyEmailSimple(userMessageForPrompt).slice(0, EMAIL_MAX_CHARS);

70-73: Coalesce nullable fields to avoid literal "null" in the prompt
Prevents confusing the model with the string "null". (Previously raised.)

-  <about>${emailAccount.about}</about>
-  <name>${emailAccount.name}</name>
+  <about>${safeAbout}</about>
+  <name>${safeName}</name>

Add above the prompt (outside this hunk):

const safeAbout = emailAccount.about ?? "";
const safeName = emailAccount.name ?? "";
🧹 Nitpick comments (5)
apps/web/utils/digest/index.ts (1)

4-7: Good move: centralize body generation with emailToContent; minor nit on nullish coalescing

  • The widened email union works with emailToContent as long as EmailForAction includes snippet (see executor.ts fix).
  • Prefer ?? over || to avoid treating empty strings as falsy.
-          to: email.headers.to || "",
+          to: email.headers.to ?? "",

Also applies to: 17-18, 38-39

apps/web/utils/ai/digest/summarize-email-for-digest.ts (4)

11-12: Trim and bound schema.content to avoid whitespace-only and oversized payloads

Adds resilience and keeps prompts/results sane.

-    .string()
+    .string()
+    .transform((s) => s.trim())
+    .max(12000, "content too long")

46-49: Heuristic for second-person summaries might over-trigger
Consider clarifying what qualifies as “direct message” (e.g., To: user, Reply-To matches sender) to avoid newsletters using “you” being miscast.


75-75: Attach contextual fields to logger and avoid PII
Adds traceability without logging content.

-  logger.info("Summarizing email for digest");
+  logger.with({ ruleName, emailAccountId: emailAccount.id }).info("Summarizing email for digest");

86-91: Use the existing withRetry helper to wrap your LLM call

I’ve confirmed there’s already a withRetry helper in your codebase (e.g. in apps/web/utils/ai/choose-rule/ai-choose-args.ts at line 84). To add a lightweight retry on transient failures, you can wrap your generateObject invocation instead of rolling your own loop.

• Import the helper where you summarise emails:

import { withRetry } from 'apps/web/utils/ai/retry'; // adjust path as needed

• Replace the bare call

const aiResponse = await generateObject({ ...modelOptions, system, prompt, schema });

with a retry-aware call:

const aiResponse = await withRetry(
  () => generateObject({ ...modelOptions, system, prompt, schema }),
  { retries: 2, factor: 250 } // 2 attempts max, 250ms backoff factor
);

This keeps your retry policy consistent and low-impact on latency, matching the rest of our AI utilities.

📜 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 ba16ff1 and afa88ec.

📒 Files selected for processing (5)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • apps/web/utils/ai/types.ts (1 hunks)
  • apps/web/utils/digest/index.ts (3 hunks)
  • apps/web/utils/scheduled-actions/executor.ts (1 hunks)
  • apps/web/utils/user/get.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/utils/user/get.ts
🧰 Additional context used
📓 Path-based instructions (9)
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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/digest/index.ts
  • apps/web/utils/ai/types.ts
  • apps/web/utils/scheduled-actions/executor.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/types.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧠 Learnings (3)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/digest/index.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
🧬 Code graph analysis (2)
apps/web/utils/digest/index.ts (3)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/ai/types.ts (1)
  • EmailForAction (4-14)
apps/web/utils/mail.ts (1)
  • emailToContent (72-101)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
⏰ 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 (2)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)

35-39: Prompt preamble LGTM
Clear, scoped, and aligned with XML-like guidance.


51-53: Structured bullets rule LGTM
Concise and user-centric.

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 (2)
packages/resend/emails/digest.tsx (2)

18-24: DigestItem.content is too narrow; support string or object.

Upstream/preview data often pass plain strings. Widen to avoid runtime errors.

 type DigestItem = {
   from: string;
   subject: string;
-  content?:
-    | {
-        type: string;
-        content: string;
-      }
-    | null
-    | undefined;
+  content?: string | { type?: string; content: string } | null | undefined;
 };

198-225: renderEmailContent assumes object shape; handle string safely.

Guard and normalize content string before splitting.

-  const renderEmailContent = (item: DigestItem) => {
-    if (!item.content?.content) return null;
-
-    const contentText = item.content.content;
+  const renderEmailContent = (item: DigestItem) => {
+    const contentText =
+      typeof item.content === "string"
+        ? item.content
+        : item.content?.content || "";
+    if (!contentText.trim()) return null;
🧹 Nitpick comments (5)
packages/resend/emails/digest.tsx (5)

84-91: Remove duplicate union member (undefined) in index signature.

It appears twice; keep it once.

-  [key: string]:
-    | NormalizedCategoryData
-    | DigestItem[]
-    | undefined
-    | string
-    | Date
-    | Record<string, string>
-    | undefined;
+  [key: string]:
+    | NormalizedCategoryData
+    | DigestItem[]
+    | string
+    | Date
+    | Record<string, string>
+    | undefined;

94-131: Subject grammar and deterministic ordering tweaks.

Make pluralization correct per category and stabilize sort when counts tie.

-  const topCategories = categoriesWithCounts
-    .sort((a, b) => b.count - a.count)
+  const topCategories = categoriesWithCounts
+    .sort((a, b) => (b.count - a.count) || a.name.localeCompare(b.name))
     .slice(0, 3);
@@
-    return `Summary of ${first.count} ${first.name.toLowerCase()} and ${second.count} ${second.name.toLowerCase()} emails`;
+    return `Summary of ${first.count} ${first.name.toLowerCase()} email${first.count === 1 ? "" : "s"} and ${second.count} ${second.name.toLowerCase()} email${second.count === 1 ? "" : "s"}`;
@@
-  return `Summary of ${first.count} ${first.name.toLowerCase()}, ${second.count} ${second.name.toLowerCase()} and ${third.count} ${third.name.toLowerCase()} emails`;
+  return `Summary of ${first.count} ${first.name.toLowerCase()} email${first.count === 1 ? "" : "s"}, ${second.count} ${second.name.toLowerCase()} email${second.count === 1 ? "" : "s"} and ${third.count} ${third.name.toLowerCase()} email${third.count === 1 ? "" : "s"}`;

Also applies to: 111-111, 125-126, 128-130


236-258: “and more” should reflect extra unique senders, not email count.

Base it on unique sender count vs shown senders.

   }) => {
-    const colors = colorClasses[categoryColors[categoryKey] || "gray"];
+    const colors = colorClasses[categoryColors[categoryKey] || "gray"];
+    const hasMoreSenders =
+      new Set(categoryData.items.map((i) => i.from)).size >
+      categoryData.senders.length;
@@
-                  {categoryData.count > 5 && " and more"}
+                  {hasMoreSenders && " and more"}
@@
-              {categoryData.count > 5 && " and more"}
+              {categoryData.count > categoryData.senders.length && " and more"}

Also applies to: 301-314


265-267: Avoid array index as React key.

Use a stable composite key to prevent reconciliation issues.

-              {categoryData.items.map((item, index) => (
-                <div key={index}>
+              {categoryData.items.map((item, index) => (
+                <div key={`${item.from}-${item.subject}`}>

753-765: URL-encode dynamic path/query parts.

Protect against unsafe characters in tokens and IDs.

-        <Link
-          href={`${baseUrl}/api/unsubscribe?token=${unsubscribeToken}`}
+        <Link
+          href={`${baseUrl}/api/unsubscribe?token=${encodeURIComponent(unsubscribeToken)}`}
@@
-        <Link
-          href={`${baseUrl}/${emailAccountId}/automation?tab=settings`}
+        <Link
+          href={`${baseUrl}/${encodeURIComponent(emailAccountId)}/automation?tab=settings`}
📜 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 afa88ec and 659686e.

📒 Files selected for processing (1)
  • packages/resend/emails/digest.tsx (10 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
!{.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.tsx
!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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • packages/resend/emails/digest.tsx
⏰ 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)
packages/resend/emails/digest.tsx (5)

27-70: Color accent (leftBorder) additions look good.

Consistent tokens and as-const typing; no concerns.


72-76: NormalizedCategoryData type is clear and minimal.

Good separation of count/senders/items for downstream rendering.


78-83: Props expansion aligns with prior learnings about wide index signatures.

Including emailAccountId and keeping the permissive index signature matches the constraints we discussed previously.


132-167: normalizeCategoryData reads well and is defensive enough.

Type guards are adequate; sender de-dupe with cap is fine.


178-186: Category color mapping is clear and readable.

Sensible defaults with gray fallback; good.

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

🧹 Nitpick comments (1)
apps/web/utils/scheduled-actions/executor.ts (1)

109-118: Preserve optional semantics for snippet; avoid coercing to empty string.

EmailForAction.snippet is optional. Using || "" erases the “absent” signal and may mislead downstream consumers that rely on undefined to mean “no snippet.”

     const emailForAction: EmailForAction = {
       threadId: message.threadId,
       id: message.id,
       headers: message.headers,
       textPlain: message.textPlain || "",
       textHtml: message.textHtml || "",
-      snippet: message.snippet || "",
+      snippet: message.snippet,
       attachments: message.attachments || [],
       internalDate: message.internalDate,
     };
📜 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 6ed6ac0 and c88e662.

📒 Files selected for processing (1)
  • apps/web/utils/scheduled-actions/executor.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.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/scheduled-actions/executor.ts
⏰ 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 (2)
apps/web/utils/scheduled-actions/executor.ts (2)

178-187: LGTM: snippet propagation keeps the field optional.

Passing snippet through without coercion aligns with the optional field design and strict-null-checks.


195-203: Remove obsolete snippet-nullability warning
The snippet field remains a non-nullable string throughout—both on ParsedMessage (snippet: string) and on EmailForAction (picked directly from ParsedMessage), and the executor’s getEmailForAction mapper uses message.snippet || "" to guarantee it’s always defined. Downstream consumers can safely perform string operations without additional nullish checks.

Likely an incorrect or invalid review comment.

@edulelis edulelis changed the title WIP: Digest prompt changes and proposed mail template v2 Digest improvements Aug 27, 2025
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 (3)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (3)

31-46: Clarify output format to guarantee a JSON object matching the Zod schema.

Explicitly require a JSON object with a content field; this reduces parse failures and drift to free-form text.

-const system = `You are an AI assistant that processes emails for inclusion in a daily digest.
-Your task is to summarize the content accordingly using the provided schema.
+const system = `You are an AI assistant that processes emails for inclusion in a daily digest.
+Your task is to summarize the content accordingly using the provided schema. Respond ONLY with a JSON object matching the schema { "content": string }.
@@
-Guidelines for summarizing the email:
+Guidelines for summarizing the email:
 - If the email is spam, promotional, or irrelevant, return "null".
@@
 - If second person phrasing is not possible or natural (e.g., for announcements, newsletters, or general updates), summarize in a clear neutral third-person style.
`;

39-46: Schema says object, prompt says “return null” → normalize “null”/empty to null for callers.

The schema only accepts { content: string }. When the model follows the prompt and returns “null” (as text), you’ll get { content: "null" }, which slips through and renders in digests. Normalize post-parse.

-    return aiResponse.object;
+    const result = aiResponse.object;
+    const normalized = (result.content ?? "").trim().toLowerCase();
+    if (!normalized || normalized === "null") {
+      return null;
+    }
+    return result;

Also applies to: 77-83


48-57: Sanitize nullable fields and bound prompt size to avoid “null” literals and context bloat.

-  const prompt = `
-<email>
-  <content>${stringifyEmailSimple(userMessageForPrompt)}</content>
-  <category>${ruleName}</category>
-</email>
-
-<user>
-  <about>${emailAccount.about}</about>
-  <name>${emailAccount.name}</name>
-</user>`;
+  const safeAbout = emailAccount.about ?? "";
+  const safeName = emailAccount.name ?? "";
+  const EMAIL_MAX_CHARS = 8000;
+  const emailContentForPrompt = stringifyEmailSimple(userMessageForPrompt).slice(0, EMAIL_MAX_CHARS);
+
+  const prompt = `
+<email>
+  <content>${emailContentForPrompt}</content>
+  <category>${ruleName}</category>
+</email>
+
+<user>
+  <about>${safeAbout}</about>
+  <name>${safeName}</name>
+</user>`;
🧹 Nitpick comments (6)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (2)

69-69: Remove console.debug calls (repo guideline: “Don't use console.”).

These leak noisy logs and violate the coding guidelines for all TS/JS files. Prefer assertions only; test logs aren’t needed here.

-    console.debug("Generated content:\n", result);
+    // Intentionally no logging in tests (keep output clean)

Also applies to: 95-95, 119-119, 157-157, 181-181, 204-204, 226-226, 249-249


49-78: Add deterministic non-AI path tests by mocking the LLM to improve reliability.

Current suite runs only when RUN_AI_TESTS is true. Add a fast, deterministic path by mocking createGenerateObject to return a fixed { content }, and a failure path to assert null on errors.

Also applies to: 80-104, 105-125, 126-137, 138-163, 164-188, 189-210, 211-232, 233-255

apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

59-66: Log with context; prefer child logger for request-scoped metadata.

Include ruleName and emailAccountId to aid tracing; reuse the child logger in catch.

-  logger.info("Summarizing email for digest");
+  const log = logger.with({ ruleName, emailAccountId: emailAccount.id });
+  log.info("Summarizing email for digest");
@@
-  } catch (error) {
-    logger.error("Failed to summarize email", { error });
+  } catch (error) {
+    log.error("Failed to summarize email", { error });

Also applies to: 77-83

packages/resend/emails/digest.tsx (3)

205-211: Avoid array index as React key in bullet lists.

Use a stable key derived from content; index keys hinder reconciliation and violate repo rules.

-            {lines.map((line: string, index: number) => (
-              <li key={index} className="text-[14px] text-gray-800 mb-[1px]">
+            {lines.map((line: string, index: number) => (
+              <li
+                key={`${line.slice(0, 40)}-${index}`}
+                className="text-[14px] text-gray-800 mb-[1px]"
+              >
                 {line.trim()}
               </li>
             ))}

259-266: Avoid array index as React key for items; derive a stable composite key.

Subject+from is stable enough for digest blocks.

-              {categoryData.items.map((item, index) => (
-                <div key={index}>
+              {categoryData.items.map((item) => (
+                <div key={`${item.from}|${item.subject}`}>

88-124: Optional: improve two/three-category subject grammar.

Currently always appends “emails”, even when counts are singular. Pluralize per-category for crisper subjects.

-  if (topCategories.length === 2) {
-    const [first, second] = topCategories;
-    return `Summary of ${first.count} ${first.name.toLowerCase()} and ${second.count} ${second.name.toLowerCase()} emails`;
-  }
+  if (topCategories.length === 2) {
+    const [a, b] = topCategories;
+    const aWord = a.count === 1 ? "email" : "emails";
+    const bWord = b.count === 1 ? "email" : "emails";
+    return `Summary of ${a.count} ${a.name.toLowerCase()} ${aWord} and ${b.count} ${b.name.toLowerCase()} ${bWord}`;
+  }
@@
-  return `Summary of ${first.count} ${first.name.toLowerCase()}, ${second.count} ${second.name.toLowerCase()} and ${third.count} ${third.name.toLowerCase()} emails`;
+  const w1 = first.count === 1 ? "email" : "emails";
+  const w2 = second.count === 1 ? "email" : "emails";
+  const w3 = third.count === 1 ? "email" : "emails";
+  return `Summary of ${first.count} ${first.name.toLowerCase()} ${w1}, ${second.count} ${second.name.toLowerCase()} ${w2} and ${third.count} ${third.name.toLowerCase()} ${w3}`;
📜 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 a0a8f3b and ecf161b.

📒 Files selected for processing (3)
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (5 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.ts (3 hunks)
  • packages/resend/emails/digest.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
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/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.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/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
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/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
!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/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • packages/resend/emails/digest.tsx
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.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/utils/ai/digest/summarize-email-for-digest.test.ts
**/*.test.{ts,js,tsx,jsx}

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

**/*.test.{ts,js,tsx,jsx}: Tests are colocated next to the tested file (e.g., dir/format.ts and dir/format.test.ts)
Use vi.mock("server-only", () => ({})); to mock the server-only module in tests
Mock @/utils/prisma in tests using vi.mock("@/utils/prisma") and use the provided prisma mock
Mock external dependencies in tests
Clean up mocks between tests
Do not mock the Logger

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.test.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.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:

  • packages/resend/emails/digest.tsx
🧠 Learnings (5)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
  • packages/resend/emails/digest.tsx
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths in LLM-related tests

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty input, null values), different user configurations, and various input formats in LLM-related tests

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
🧬 Code graph analysis (2)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (2)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmailSimple (29-37)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (3)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/types.ts (1)
  • EmailForLLM (117-131)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)
  • aiSummarizeEmailForDigest (17-83)
⏰ 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 (1)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)

23-25: Type change ripple: ensure all callers pass name at the top level.

Routes should use getEmailAccountWithAiAndName(...). Tests must set name as shown in the companion comment.

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

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/digest/summarize-email-for-digest.test.ts (1)

105-124: Add schema validation for consistency with AI guidelines

All AI responses should be schema-validated (present in earlier tests but missing here).

   expect(result).toMatchObject({
     content: expect.any(String),
   });
+  const validationResult = DigestEmailSummarySchema.safeParse(result);
+  expect(validationResult.success).toBe(true);
🧹 Nitpick comments (3)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (3)

16-16: Gate AI tests on API key presence to avoid false failures

If RUN_AI_TESTS is true but OPENAI_API_KEY is unset, these tests will likely fail. Gate on both.

-const isAiTest = process.env.RUN_AI_TESTS === "true";
+const isAiTest =
+  process.env.RUN_AI_TESTS === "true" && !!process.env.OPENAI_API_KEY;

18-36: Type overrides for helpers to improve DX and safety

Make overrides a Partial<EmailAccountForDigest> and annotate return type.

-function getEmailAccount(overrides = {}): EmailAccountForDigest {
+function getEmailAccount(
+  overrides: Partial<EmailAccountForDigest> = {},
+): EmailAccountForDigest {
   return {
     id: "email-account-id",
     userId: "user1",
     email: "user@test.com",
     about: "Software engineer working on email automation",
     name: "Test User",
     account: {
       provider: "gmail",
     },
     user: {
       aiModel: "gpt-4",
       aiProvider: "openai",
       aiApiKey: process.env.OPENAI_API_KEY || null,
     },
     ...overrides,
   };
 }

38-47: Mirror the override typing for email factory

Use Partial<EmailForLLM> for overrides and annotate return type.

-function getTestEmail(overrides = {}): EmailForLLM {
+function getTestEmail(
+  overrides: Partial<EmailForLLM> = {},
+): EmailForLLM {
   return {
     id: "email-id",
     from: "sender@example.com",
     to: "user@test.com",
     subject: "Test Email",
     content: "This is a test email content",
     ...overrides,
   };
 }
📜 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 ecf161b and f89ec8c.

📒 Files selected for processing (3)
  • apps/web/app/api/ai/digest/route.ts (2 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (5 hunks)
  • apps/web/utils/user/get.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/utils/user/get.ts
  • apps/web/app/api/ai/digest/route.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/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/utils/ai/digest/summarize-email-for-digest.test.ts
**/*.test.{ts,js,tsx,jsx}

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

**/*.test.{ts,js,tsx,jsx}: Tests are colocated next to the tested file (e.g., dir/format.ts and dir/format.test.ts)
Use vi.mock("server-only", () => ({})); to mock the server-only module in tests
Mock @/utils/prisma in tests using vi.mock("@/utils/prisma") and use the provided prisma mock
Mock external dependencies in tests
Clean up mocks between tests
Do not mock the Logger

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.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/ai/digest/summarize-email-for-digest.test.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
🧠 Learnings (3)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
PR: elie222/inbox-zero#576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty input, null values), different user configurations, and various input formats in LLM-related tests

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths in LLM-related tests

Applied to files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
🧬 Code graph analysis (1)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (3)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/types.ts (1)
  • EmailForLLM (117-131)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)
  • aiSummarizeEmailForDigest (17-83)
🔇 Additional comments (4)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (4)

7-10: Type alias matches function contract — good fix

Aligns tests with aiSummarizeEmailForDigest signature (EmailAccountWithAI & { name: string | null }). This resolves earlier name-shape mismatches.


54-78: Order-details test looks solid; schema validated — LGTM


80-104: Meeting-notes test: schema validation present — LGTM


126-137: Covers non-AI early-return path — LGTM

@@ -46,37 +69,22 @@ describe.runIf(isAiTest)("aiSummarizeEmailForDigest", () => {
console.debug("Generated content:\n", result);
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

Remove console.debug — violates repo guideline “Don't use console.”

These logs will fail linting in CI.

-    console.debug("Generated content:\n", result);
-    console.debug("Generated content:\n", result);
-    console.debug("Generated content:\n", result);
-    console.debug(`Generated content for ${category}:\n`, result);
-    console.debug("Generated content:\n", result);
-    console.debug("Generated content:\n", result);
-    console.debug("Generated content:\n", result);
-    console.debug("Generated content:\n", result);

Also applies to: 95-95, 119-119, 157-157, 181-181, 204-204, 226-226, 249-249

🤖 Prompt for AI Agents
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts lines
69,95,119,157,181,204,226,249: remove the console.debug(...) calls (they violate
the "Don't use console." guideline and fail CI linting); either delete those
debug statements or replace them with the repo-approved logger API (or
Jest-friendly logging/mocks) and ensure any replacement respects lint rules,
then run tests/lint to confirm no console usage remains.

Comment on lines +138 to +163
test("handles different user configurations", async () => {
const emailAccount = getEmailAccount({
about: "Marketing manager focused on customer engagement",
name: "Marketing User",
});

const messageToSummarize = getTestEmail({
from: "newsletter@company.com",
subject: "Weekly Marketing Update",
content:
"Invoice #INV-2024-001\nAmount: $150.00\nDue Date: 2024-04-01\nStatus: Pending",
};
"This week's marketing metrics: Email open rate: 25%, Click-through rate: 3.2%, Conversion rate: 1.8%",
});

const result = await aiSummarizeEmailForDigest({
ruleName: "invoice",
ruleName: "newsletter",
emailAccount,
messageToSummarize,
});

console.debug("Generated content:\n", result);

// Verify the result matches the schema
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);

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

Validate schema in “different user configurations” test

Keep tests uniform and catch schema regressions early.

   expect(result).toMatchObject({
     content: expect.any(String),
   });
+  const validationResult = DigestEmailSummarySchema.safeParse(result);
+  expect(validationResult.success).toBe(true);
📝 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
test("handles different user configurations", async () => {
const emailAccount = getEmailAccount({
about: "Marketing manager focused on customer engagement",
name: "Marketing User",
});
const messageToSummarize = getTestEmail({
from: "newsletter@company.com",
subject: "Weekly Marketing Update",
content:
"Invoice #INV-2024-001\nAmount: $150.00\nDue Date: 2024-04-01\nStatus: Pending",
};
"This week's marketing metrics: Email open rate: 25%, Click-through rate: 3.2%, Conversion rate: 1.8%",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "invoice",
ruleName: "newsletter",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
// Verify the result matches the schema
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);
test("handles different user configurations", async () => {
const emailAccount = getEmailAccount({
about: "Marketing manager focused on customer engagement",
name: "Marketing User",
});
const messageToSummarize = getTestEmail({
from: "newsletter@company.com",
subject: "Weekly Marketing Update",
content:
"This week's marketing metrics: Email open rate: 25%, Click-through rate: 3.2%, Conversion rate: 1.8%",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "newsletter",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
expect(result).toMatchObject({
content: expect.any(String),
});
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
}, 15_000);

Comment on lines +164 to +188
test("handles various email categories correctly", async () => {
const emailAccount = getEmailAccount();
const categories = ["invoice", "receipt", "travel", "notification"];

for (const category of categories) {
const messageToSummarize = getTestEmail({
from: `${category}@example.com`,
subject: `Test ${category} email`,
content: `This is a test ${category} email with sample content`,
});

const result = await aiSummarizeEmailForDigest({
ruleName: category,
emailAccount,
messageToSummarize,
});

console.debug(`Generated content for ${category}:\n`, result);

expect(result).toMatchObject({
content: expect.any(String),
});
}
}, 30_000);

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

Validate schema inside the categories loop

Strengthens coverage across categories.

       expect(result).toMatchObject({
         content: expect.any(String),
       });
+      const validationResult = DigestEmailSummarySchema.safeParse(result);
+      expect(validationResult.success).toBe(true);
📝 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
test("handles various email categories correctly", async () => {
const emailAccount = getEmailAccount();
const categories = ["invoice", "receipt", "travel", "notification"];
for (const category of categories) {
const messageToSummarize = getTestEmail({
from: `${category}@example.com`,
subject: `Test ${category} email`,
content: `This is a test ${category} email with sample content`,
});
const result = await aiSummarizeEmailForDigest({
ruleName: category,
emailAccount,
messageToSummarize,
});
console.debug(`Generated content for ${category}:\n`, result);
expect(result).toMatchObject({
content: expect.any(String),
});
}
}, 30_000);
test("handles various email categories correctly", async () => {
const emailAccount = getEmailAccount();
const categories = ["invoice", "receipt", "travel", "notification"];
for (const category of categories) {
const messageToSummarize = getTestEmail({
from: `${category}@example.com`,
subject: `Test ${category} email`,
content: `This is a test ${category} email with sample content`,
});
const result = await aiSummarizeEmailForDigest({
ruleName: category,
emailAccount,
messageToSummarize,
});
console.debug(`Generated content for ${category}:\n`, result);
expect(result).toMatchObject({
content: expect.any(String),
});
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
}
}, 30_000);
🤖 Prompt for AI Agents
In apps/web/utils/ai/digest/summarize-email-for-digest.test.ts around lines
164-188, add schema validation inside the categories loop after obtaining
result: call the project’s existing digest/schema validator (e.g.,
validateDigestSchema(result) or equivalent) or add explicit expects for required
fields (e.g., expect(result).toMatchObject({ content: expect.any(String), title:
expect.any(String) }) and any other required keys), ensuring the result conforms
to the digest schema for each category before finishing the iteration.

Comment on lines +189 to +210
test("handles promotional emails appropriately", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "promotions@store.com",
subject: "50% OFF Everything! Limited Time Only!",
content:
"Don't miss our biggest sale of the year! Everything is 50% off for the next 24 hours only!",
});

const result = await aiSummarizeEmailForDigest({
ruleName: "marketing",
emailAccount,
messageToSummarize,
});

console.debug("Generated content:\n", result);

expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);

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

Align promotional-email expectation with prompt rule (“return null”)

System prompt instructs: spam/promotional → return "null". Adjust assertion accordingly.

-  expect(result).toMatchObject({
-    content: expect.any(String),
-  });
+  const validationResult = DigestEmailSummarySchema.safeParse(result);
+  expect(validationResult.success).toBe(true);
+  expect(result?.content?.toLowerCase()).toBe("null");
📝 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
test("handles promotional emails appropriately", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "promotions@store.com",
subject: "50% OFF Everything! Limited Time Only!",
content:
"Don't miss our biggest sale of the year! Everything is 50% off for the next 24 hours only!",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "marketing",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);
test("handles promotional emails appropriately", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "promotions@store.com",
subject: "50% OFF Everything! Limited Time Only!",
content:
"Don't miss our biggest sale of the year! Everything is 50% off for the next 24 hours only!",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "marketing",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
// Validate against our Zod schema and assert promotional rule returns "null"
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
expect(result?.content?.toLowerCase()).toBe("null");
}, 15_000);
🤖 Prompt for AI Agents
In apps/web/utils/ai/digest/summarize-email-for-digest.test.ts around lines 189
to 210, the test currently expects a string content for promotional emails, but
the system prompt specifies spam/promotional messages should return null; update
the assertion to expect result toBeNull or toMatchObject({ content: null })
consistent with how the function returns null (or adjust to
expect(result).toBeNull() if the function returns null directly), and remove or
change any debug/expectations that assume a string so the test aligns with the
prompt rule.

Comment on lines +211 to +232
test("handles direct messages to user in second person", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "hr@company.com",
subject: "Your Annual Review is Due",
content:
"Hi Test User, Your annual performance review is due by Friday. Please complete the self-assessment form and schedule a meeting with your manager.",
});

const result = await aiSummarizeEmailForDigest({
ruleName: "hr",
emailAccount,
messageToSummarize,
});

console.debug("Generated content:\n", result);

expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);

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

Assert second-person phrasing for direct messages

Prompt requires second-person summaries for direct messages.

   expect(result).toMatchObject({
     content: expect.any(String),
   });
+  const validationResult = DigestEmailSummarySchema.safeParse(result);
+  expect(validationResult.success).toBe(true);
+  expect(result?.content).toMatch(/\bYou\b/i);
📝 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
test("handles direct messages to user in second person", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "hr@company.com",
subject: "Your Annual Review is Due",
content:
"Hi Test User, Your annual performance review is due by Friday. Please complete the self-assessment form and schedule a meeting with your manager.",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "hr",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);
test("handles direct messages to user in second person", async () => {
const emailAccount = getEmailAccount();
const messageToSummarize = getTestEmail({
from: "hr@company.com",
subject: "Your Annual Review is Due",
content:
"Hi Test User, Your annual performance review is due by Friday. Please complete the self-assessment form and schedule a meeting with your manager.",
});
const result = await aiSummarizeEmailForDigest({
ruleName: "hr",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
expect(result).toMatchObject({
content: expect.any(String),
});
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);
expect(result?.content).toMatch(/\bYou\b/i);
}, 15_000);
🤖 Prompt for AI Agents
In apps/web/utils/ai/digest/summarize-email-for-digest.test.ts around lines
211-232, the test only asserts the summary is a string but the prompt requires
second-person phrasing for direct messages; update the test to assert the
generated summary addresses the user in second person by adding an expectation
such as expect(result.content).toMatch(/\byou\b/i) or
expect(result.content).toMatch(/\b(you|your|you'll|you’re|you've)\b/i) (and
optionally assert it does not use third-person pronouns), keeping the existing
flow and timeout.

Comment on lines +233 to 255
test("handles edge case with very long email content", async () => {
const emailAccount = getEmailAccount();
const longContent = `${"This is a very long email content. ".repeat(100)}End of long content.`;

const messageToSummarize = getTestEmail({
from: "long@example.com",
subject: "Very Long Email",
content: longContent,
});

const result = await aiSummarizeEmailForDigest({
ruleName: "other",
emailAccount,
messageToSummarize,
});

console.debug("Generated content:\n", result);

expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);
});
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

Add schema validation for long-content case

Ensures truncation/formatting changes don’t break the contract.

   expect(result).toMatchObject({
     content: expect.any(String),
   });
+  const validationResult = DigestEmailSummarySchema.safeParse(result);
+  expect(validationResult.success).toBe(true);
📝 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
test("handles edge case with very long email content", async () => {
const emailAccount = getEmailAccount();
const longContent = `${"This is a very long email content. ".repeat(100)}End of long content.`;
const messageToSummarize = getTestEmail({
from: "long@example.com",
subject: "Very Long Email",
content: longContent,
});
const result = await aiSummarizeEmailForDigest({
ruleName: "other",
emailAccount,
messageToSummarize,
});
console.debug("Generated content:\n", result);
expect(result).toMatchObject({
content: expect.any(String),
});
}, 15_000);
});
expect(result).toMatchObject({
content: expect.any(String),
});
const validationResult = DigestEmailSummarySchema.safeParse(result);
expect(validationResult.success).toBe(true);

@elie222 elie222 merged commit 5c69411 into elie222:main Aug 28, 2025
8 of 9 checks passed
@edulelis edulelis deleted the digest-emails-v15 branch August 29, 2025 14:37
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.

2 participants

Comments