Conversation
|
@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. |
WalkthroughConsolidates AI digest summaries to a single-string Changes
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
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 correctlyThe digest generator now returns a plain string, so storing it with
JSON.stringifywill persist extra quotes and escaped newlines—and the existing resend endpoint usesJSON.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
contentis 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 carryemailAccountIdandmessageId.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
aboutoruser.nameis 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("&", "&").replaceAll("<", "<").replaceAll(">", ">");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.
📒 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.tsapps/web/utils/user/get.tsapps/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: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.tspackages/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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.tspackages/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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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 useelements 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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.tspackages/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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.tspackages/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.tsapps/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.tsapps/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 operationsApplied to files:
apps/web/app/api/ai/digest/route.tsapps/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.tspackages/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
renderEmailContentpath aligns with the summarizer’s string output.
There was a problem hiding this comment.
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
esModuleInteropis enabled; otherwise the default import ofpluralizewill 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
esModuleInteropis 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; useimport pluralize = require("pluralize")instead. Otherwise this is fine.
73-86: Index signature includesundefinedtwice; 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
uniqueSenderCountduring 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
uniqueSenderCountto 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.
normalizeCategoryDatais called once forhasItemsand again here. Cache results in aMap<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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.jsonapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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.jsonapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tspackages/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.tsanddir/format.test.ts)
Usevi.mock("server-only", () => ({}));to mock theserver-onlymodule in tests
Mock@/utils/prismain tests usingvi.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 useelements 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.tspackages/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 testsApplied 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 commentsApplied 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.
|
Remove WIP when done |
There was a problem hiding this comment.
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 bundlingThis 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 replacementsNode 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 stringIf 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 developmentThis 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 publishSequential 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 semanticspublishToQstashQueue’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 butvoidwhen falling back tofetch:
publishToQstashbulkPublishToQstashpublishToQstashQueueAlthough all existing call sites simply
awaitthese 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
voidby 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 fieldsIn either case, update the function signatures to explicitly declare the unified return type (e.g.
Promise<void>orPromise<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.
📒 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.tsapps/web/utils/upstash/signature.tsapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/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.tsapps/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: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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.tsapps/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.tsapps/web/utils/upstash/signature.tspackages/resend/src/send.tsxapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/app/api/ai/digest/route.tspackages/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.tsapps/web/utils/upstash/signature.tsapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/app/api/ai/digest/route.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/upstash/signature.tspackages/resend/src/send.tsxapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/app/api/ai/digest/route.tspackages/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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/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.tsapps/web/app/api/ai/digest/route.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/utils/upstash/signature.tspackages/resend/src/send.tsxapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/app/api/ai/digest/route.tspackages/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.tsapps/web/utils/upstash/signature.tspackages/resend/src/send.tsxapps/web/utils/upstash/index.tsapps/web/utils/ai/digest/summarize-email-for-digest.tsapps/web/app/api/ai/digest/route.tspackages/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.tsapps/web/utils/upstash/index.tsapps/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.tsapps/web/utils/upstash/index.tsapps/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.tsxpackages/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.tsxpackages/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.tsxpackages/resend/emails/digest.tsxapps/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 callFiles:
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.tspackages/resend/src/send.tsxapps/web/utils/ai/digest/summarize-email-for-digest.tspackages/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 operationsApplied to files:
apps/web/app/api/resend/digest/route.tsapps/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.tsapps/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.objectApplied 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 claimsAfter digging into the QStash Next.js integration:
- The JWT payload key for the body digest is indeed “body,” which matches the
Receiver.verifyimplementation’s expectations when it validates the HMAC overheader.payload(the verifier reads thebodyclaim directly) (github.com).- The
subclaim is never checked byverifySignatureAppRouter(it only callsreceiver.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
subvalues won’t break signature validation. Feel free to resolve this review.
There was a problem hiding this comment.
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
contentfield.**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 flattenedtokenscopy. That’s convenient but redundant. Consider returning onlytokens(or onlyaccount) 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 usingwithRetryThe
withRetryhelper is already defined (e.g. inapps/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 thegenerateObjectcall (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.
📒 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.tsapps/web/utils/user/get.tsapps/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.tsapps/web/utils/user/get.tsapps/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.tsapps/web/utils/user/get.tsapps/web/utils/ai/digest/summarize-email-for-digest.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/user/get.tsapps/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.tsapps/web/utils/user/get.tsapps/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.tsapps/web/utils/user/get.tsapps/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 useelements 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.tsapps/web/utils/user/get.tsapps/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.tsapps/web/utils/user/get.tsapps/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.tsapps/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 foruser.namein promptsThe Prisma schema defines
User.nameas nullable (String?), soEmailAccountWithAI['name']is typed asstring | 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 instring | null)
• apps/web/api/summarize-email-for-digest.ts – wrapuser.nameusage 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.
There was a problem hiding this comment.
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 inwithRetryand Enrich LogsThe project already provides a
withRetryhelper (in apps/web/utils/llms/index.ts) – import and use it to retry transient failures when callinggenerateObject. After the call, log a fixed message withruleNameanduserEmailfor better traceability (avoid logging prompt/content).• File: apps/web/utils/ai/digest/summarize-email-for-digest.ts (around lines 77–92)
– ImportwithRetryfrom your LLM utils
– Wrap thegenerateObjectcall
– Add an info-level log withruleNameanduserEmailExample 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.
📒 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.tsapps/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.tsapps/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.tsapps/web/utils/ai/digest/summarize-email-for-digest.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/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.tsapps/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.tsapps/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 useelements 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.tsapps/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.tsapps/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 updategetEmailAccountWithAicall sites for the addednamefield
Since you’ve introducedgetEmailAccountWithAiAndName(which now returnsuser.name), please ensure that any existing callers of the oldgetEmailAccountWithAihelper either switch to the new function or explicitly handle thenamefield.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 handleuser.name).
There was a problem hiding this comment.
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 requiredsnippetfield in the firstEmailForActioninstantiationThe
EmailForActionliteral in apps/web/utils/scheduled-actions/executor.ts around line 109 is missing the newly addedsnippetproperty. 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 109const 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 includessnippet. 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.snippetmay be undefined — populate it earlierYou read
snippethere, butvalidateEmailStatenever sets it, risking undefined content and breaking downstream logic relying onemailToContent.Patch
validateEmailStateto includesnippet: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 withemailToContent; minor nit on nullish coalescing
- The widened
emailToContentas long asEmailForActionincludessnippet(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 payloadsAdds 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 existingwithRetryhelper to wrap your LLM callI’ve confirmed there’s already a
withRetryhelper in your codebase (e.g. inapps/web/utils/ai/choose-rule/ai-choose-args.tsat line 84). To add a lightweight retry on transient failures, you can wrap yourgenerateObjectinvocation 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.
📒 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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/web/utils/ai/digest/summarize-email-for-digest.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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 useelements 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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/web/utils/ai/types.tsapps/web/utils/scheduled-actions/executor.tsapps/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.tsapps/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.tsapps/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.
There was a problem hiding this comment.
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.
📒 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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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 useelements 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.
There was a problem hiding this comment.
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.snippetis optional. Using|| ""erases the “absent” signal and may mislead downstream consumers that rely onundefinedto 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.
📒 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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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 useelements 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
snippetthrough without coercion aligns with the optional field design and strict-null-checks.
195-203: Remove obsolete snippet-nullability warning
Thesnippetfield remains a non-nullable string throughout—both on ParsedMessage (snippet: string) and on EmailForAction (picked directly from ParsedMessage), and the executor’sgetEmailForActionmapper usesmessage.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.
There was a problem hiding this comment.
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
contentfield; 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 tonullfor 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: Removeconsole.debugcalls (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
createGenerateObjectto return a fixed{ content }, and a failure path to assertnullon 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
ruleNameandemailAccountIdto 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.
📒 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.tsapps/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.tsapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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.tsapps/web/utils/ai/digest/summarize-email-for-digest.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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.tsapps/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.tsapps/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 useelements 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.tsapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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.tsapps/web/utils/ai/digest/summarize-email-for-digest.test.tspackages/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.tsapps/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.tsanddir/format.test.ts)
Usevi.mock("server-only", () => ({}));to mock theserver-onlymodule in tests
Mock@/utils/prismain tests usingvi.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.tspackages/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 consistentApplied 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.objectApplied 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 testsApplied 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 testsApplied 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 passnameat the top level.Routes should use
getEmailAccountWithAiAndName(...). Tests must setnameas shown in the companion comment.
There was a problem hiding this comment.
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 guidelinesAll 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 failuresIf
RUN_AI_TESTSis true butOPENAI_API_KEYis 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 safetyMake
overridesaPartial<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 factoryUse
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.
📒 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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsanddir/format.test.ts)
Usevi.mock("server-only", () => ({}));to mock theserver-onlymodule in tests
Mock@/utils/prismain tests usingvi.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 useelements 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 fixAligns tests with
aiSummarizeEmailForDigestsignature (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); | |||
There was a problem hiding this comment.
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.
| 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); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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); |
| 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); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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); | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| 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); | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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); | ||
| }); |
There was a problem hiding this comment.
🛠️ 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.
| 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); |
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Tests