Skip to content

Add Fastmail support using JMAP API#1110

Open
marcodejongh wants to merge 14 commits intoelie222:mainfrom
marcodejongh:claude/add-fastmail-support-bu4nM
Open

Add Fastmail support using JMAP API#1110
marcodejongh wants to merge 14 commits intoelie222:mainfrom
marcodejongh:claude/add-fastmail-support-bu4nM

Conversation

@marcodejongh
Copy link

@marcodejongh marcodejongh commented Dec 18, 2025

this is a first claude code pass, will have to try and deploy this tomorrow.

This implements Fastmail as a third email provider alongside Gmail and Microsoft/Outlook. Uses the JMAP (JSON Meta Application Protocol) which is Fastmail's native API.

Key changes:

  • Add FastmailProvider class implementing the EmailProvider interface
  • Add JMAP client utilities for session management and API calls
  • Add OAuth flow routes for Fastmail account linking
  • Update provider factory to support Fastmail
  • Add Fastmail to UI account linking options
  • Add environment variables for FASTMAIL_CLIENT_ID and FASTMAIL_CLIENT_SECRET

Features supported:

  • Email read/send/reply/forward
  • Thread management (archive, trash, mark read)
  • Mailbox/folder management (JMAP uses mailboxes instead of Gmail labels)
  • Bulk operations
  • Attachments

Note: Some features like Gmail-style filters and Pub/Sub watching use different mechanisms in JMAP and have limited/no support initially.

Summary by CodeRabbit

  • New Features

    • Full Fastmail support: OAuth linking, app-token linking modal, Add Fastmail account UI, Fastmail provider button, and comprehensive JMAP mail handling (inbox, send, drafts, labels, attachments).
    • Per-provider loading states and a global disable while any provider is linking.
    • Optional Authelia sign-in button when enabled.
  • Chores

    • Added Fastmail and Authelia env flags and a Fastmail feature-flag hook.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 18, 2025

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

@CLAassistant
Copy link

CLAassistant commented Dec 18, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ marcodejongh
❌ claude
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Adds Fastmail and Authelia support: new Fastmail JMAP client and EmailProvider, OAuth auth-url and callback routes, app-token linking action and modal, UI wiring and provider-driven loading in AddAccount, feature flags/env entries, and support utilities across auth and account-linking.

Changes

Cohort / File(s) Change Summary
UI Provider Support
apps/web/app/(app)/accounts/AddAccount.tsx, apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
Add Fastmail connect button and "Use App Token" modal; introduce Provider type, per-provider loading map and isAnyLoading, provider display-name map, provider-specific messaging, and modal open-state wiring.
Fastmail OAuth Routes
apps/web/app/api/fastmail/linking/auth-url/route.ts, apps/web/app/api/fastmail/linking/callback/route.ts
New auth-url GET route building OAuth URL and setting state cookie; callback GET validates state, uses cache/lock, exchanges code, fetches userinfo, and handles create/update/merge linking flows with redirects, caching and error handling.
Fastmail Client & Constants
apps/web/utils/fastmail/client.ts, apps/web/utils/fastmail/constants.ts, apps/web/utils/fastmail/scopes.ts
New Fastmail JMAP + OAuth client with fetchWithRetry/backoff, JMAP types/session/request helpers, token refresh/persistence, userinfo helpers, OAuth config, session/token endpoints, state-cookie constant, mailbox role constants, and SCOPES.
Fastmail Email Provider
apps/web/utils/email/fastmail.ts
New FastmailProvider implementing EmailProvider via JMAP: mailbox discovery/cache, thread/message retrieval, labels/folders, drafts/sending/reply/forward, attachments, identities/signatures, pagination, and many helper methods.
Provider Types & Factory
apps/web/utils/email/types.ts, apps/web/utils/email/provider-types.ts, apps/web/utils/email/provider.ts
Add "fastmail" to EmailProvider.name, add isFastmailProvider helper, and construct FastmailProvider in provider factory.
Account Linking & Helpers
apps/web/utils/account-linking.ts, apps/web/utils/account.ts, apps/web/utils/auth.ts, apps/web/utils/oauth/account-linking.ts, apps/web/utils/actions/fastmail-app-token.ts, apps/web/utils/actions/fastmail-app-token.validation.ts
Expand provider unions to include Fastmail; add getFastmailClientForEmail/getFastmailAndAccessTokenForEmail; map Fastmail profile data; add server action linkFastmailAppTokenAction and Zod validation for app-token linking (create/update/merge handling).
Auth Client & Login UI
apps/web/utils/auth-client.ts, apps/web/app/(landing)/login/LoginForm.tsx
Add genericOAuthClient plugin for Authelia and conditional "Sign in with Authelia" button controlled by NEXT_PUBLIC_AUTHELIA_ENABLED.
Redirects / Onboarding
apps/web/app/(landing)/welcome-redirect/page.tsx
Add check for existing emailAccount and redirect to /accounts when none found before onboarding flow.
Env, Examples & Tooling
apps/web/env.ts, apps/web/.env.example, turbo.json
Add FASTMAIL and Authelia env entries and feature flags; expose NEXT_PUBLIC_FASTMAIL_ENABLED runtime flag; add FASTMAIL env entries to turbo build environment.
Misc UI/provider tweaks
apps/web/providers/EmailProvider.tsx, apps/web/utils/email.ts, apps/web/hooks/useFeatureFlags.ts
Map Fastmail to undefined label color, export isValidEmail, and add useFastmailEnabled() hook to read NEXT_PUBLIC_FASTMAIL_ENABLED.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as AddAccount UI
    participant AuthURL as /api/fastmail/linking/auth-url
    participant FastmailOAuth as Fastmail OAuth
    participant Callback as /api/fastmail/linking/callback
    participant FastmailJMAP as Fastmail JMAP
    participant ServerAction as linkFastmailAppTokenAction
    participant DB as Database

    User->>UI: Click "Add Fastmail" (OAuth) or "Use App Token"
    alt OAuth flow
        UI->>AuthURL: GET auth-url
        AuthURL->>AuthURL: generate state & set state cookie
        AuthURL-->>UI: return { url }
        UI->>FastmailOAuth: redirect user to url
        FastmailOAuth-->>User: consent prompt
        User->>FastmailOAuth: authorize (redirect with code,state)
        FastmailOAuth->>Callback: deliver code,state
        Callback->>Callback: validate state cookie
        Callback->>Callback: acquire lock / check cache
        Callback->>FastmailOAuth: exchange code -> tokens
        Callback->>FastmailJMAP: get userinfo (sub,email,name)
        Callback->>DB: lookup linked account
        alt existing account
            Callback->>DB: update tokens
        else new account
            Callback->>DB: create account + emailAccount
        end
        Callback->>Callback: cache result, release lock, clear state cookie
        Callback-->>User: redirect to /accounts with status
    else App Token flow
        UI->>ServerAction: submit app token
        ServerAction->>FastmailJMAP: validate token -> userinfo
        ServerAction->>DB: lookup/handle create/update/merge
        ServerAction-->>UI: return result (success/error)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

  • Areas needing focused review:
    • apps/web/utils/email/fastmail.ts — extensive JMAP surface and edge cases.
    • apps/web/app/api/fastmail/linking/callback/route.ts — state validation, distributed locking, caching, token exchange, redirection.
    • apps/web/utils/fastmail/client.ts — retry/backoff, session handling, token refresh/persistence.
    • apps/web/utils/actions/fastmail-app-token.ts — linking/create/update/merge logic and concurrency handling.
    • Cross-cutting: provider-type changes, env/runtime flags, UI ↔ server-action interactions, and added public exports.

Possibly related PRs

Poem

🐰 I found a token in a burrowed nook,

I hopped through JMAP and read every book,
I set the cookie, jingled state with glee,
I stitched an inbox bridge — now mail hops to me! 🥕📬

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Fastmail support via JMAP API, which is reflected across the entire changeset including the new Fastmail provider implementation, JMAP client utilities, OAuth routes, and UI integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp
Copy link
Contributor

macroscopeapp bot commented Dec 18, 2025

Add Fastmail email account support using the JMAP API across provider factory, OAuth/app-token linking flows, and a new FastmailProvider in fastmail.ts

Introduce a JMAP-backed FastmailProvider, extend the provider factory to return it, add OAuth and app‑token linking endpoints and UI, and wire env flags and scopes for runtime config. Core HTTP/JMAP utilities handle retries, token refresh, and error surfacing.

📍Where to Start

Start with the FastmailProvider in fastmail.ts, then review client construction and JMAP utilities in client.ts, and the provider selection in provider.ts.


Macroscope summarized 9fb10b9.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

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/account.ts (1)

23-36: Add await when calling getGmailClientWithRefresh in getGmailClientForEmail.

getGmailClientWithRefresh is async, but line 29 is missing await. The other client functions—getOutlookClientForEmail (line 60) and getFastmailClientForEmail (line 113)—correctly await their respective *WithRefresh calls. Without await, the function returns a Promise instead of the resolved client, causing downstream errors.

🧹 Nitpick comments (9)
apps/web/utils/account-linking.ts (1)

39-42: Consider simplifying the response type union.

All three response types (GetAuthLinkUrlResponse, GetOutlookAuthLinkUrlResponse, GetFastmailAuthLinkUrlResponse) have the identical shape { url: string }. You could simplify this to a single inline type or a shared type alias.

🔎 Optional simplification:
-  const data:
-    | GetAuthLinkUrlResponse
-    | GetOutlookAuthLinkUrlResponse
-    | GetFastmailAuthLinkUrlResponse = await response.json();
+  const data: { url: string } = await response.json();

Alternatively, define a shared AuthLinkUrlResponse type in a common location.

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

307-368: Consider adding explicit handling for unsupported providers.

The getProfileData function implicitly returns undefined when the provider doesn't match any known type. This could lead to confusing runtime behavior if a new provider is added but this function isn't updated. Consider adding an explicit throw for unknown providers.

🔎 Suggested improvement:
   if (isFastmailProvider(providerId)) {
     try {
       const userInfo = await getFastmailUserInfo(accessToken);
       return {
         email: userInfo.email?.toLowerCase(),
         name: userInfo.name,
         image: undefined, // Fastmail doesn't provide profile photos via OIDC
       };
     } catch (error) {
       logger.error("Error fetching Fastmail profile data", { error });
       throw error;
     }
   }
+
+  throw new Error(`Unsupported provider: ${providerId}`);
 }
apps/web/app/(app)/accounts/AddAccount.tsx (1)

85-94: Consider using an official Fastmail brand asset.

The implementation is correct, but the generic MailIcon with a hardcoded purple color (#5c2d91) differs from Google/Microsoft which use official SVG brand icons. For visual consistency and brand accuracy, consider adding a Fastmail logo SVG to /images/fastmail.svg.

apps/web/app/api/fastmail/linking/callback/route.ts (1)

88-96: Consider not including status code in error message.

The error message includes the HTTP status code which could leak implementation details. While this is an internal error, consider using a more generic message.

🔎 Suggested change:
-      throw new Error(
-        `Failed to exchange code for tokens: ${tokenResponse.status}`,
-      );
+      throw new Error("Failed to exchange code for tokens");
apps/web/utils/email/fastmail.ts (5)

2275-2283: Inefficient base64 encoding for large attachments.

String concatenation in a loop is O(n²) due to string immutability. For large attachments, this could cause significant performance degradation and memory pressure.

🔎 Use Buffer for efficient encoding (Node.js environment):
     const buffer = await response.arrayBuffer();
-    // Convert ArrayBuffer to base64 using Web APIs
-    const bytes = new Uint8Array(buffer);
-    let binary = "";
-    for (let i = 0; i < bytes.byteLength; i++) {
-      binary += String.fromCharCode(bytes[i]);
-    }
-    const base64 = btoa(binary);
+    // Use Buffer for efficient base64 encoding
+    const base64 = Buffer.from(buffer).toString("base64");

     return {
       data: base64,
       size: buffer.byteLength,
     };

1682-1691: Consider batching label removals.

Iterating and making separate API calls for each label is inefficient. JMAP supports multiple patches in a single update object.

🔎 Batch into single request:
   async removeThreadLabels(
     threadId: string,
     labelIds: string[],
   ): Promise<void> {
     if (labelIds.length === 0) return;

-    for (const labelId of labelIds) {
-      await this.removeThreadLabel(threadId, labelId);
-    }
+    // Get all emails in the thread
+    const threadResponse = await this.client.request([
+      [
+        "Email/query",
+        {
+          accountId: this.client.accountId,
+          filter: { inThread: threadId },
+        },
+        "0",
+      ],
+    ]);
+
+    const emailIds = getResponseData<JMAPQueryResponse>(
+      threadResponse.methodResponses[0],
+    ).ids;
+
+    if (emailIds.length === 0) return;
+
+    // Remove all labels from all emails in one request
+    const update: Record<string, Record<string, boolean>> = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {};
+      for (const labelId of labelIds) {
+        update[emailId][`mailboxIds/${labelId}`] = false;
+      }
+    }
+
+    await this.client.request([
+      [
+        "Email/set",
+        {
+          accountId: this.client.accountId,
+          update,
+        },
+        "0",
+      ],
+    ]);
   }

950-1004: Redundant mailbox lookups inside loop.

getMailboxByRole(FastmailMailbox.INBOX) and getMailboxByRole(FastmailMailbox.ARCHIVE) are called for each sender iteration, but the results don't change. Move these lookups outside the loop.

🔎 Apply this diff:
   async bulkArchiveFromSenders(
     fromEmails: string[],
     ownerEmail: string,
     _emailAccountId: string,
   ): Promise<void> {
     const log = this.logger.with({
       action: "bulkArchiveFromSenders",
       sendersCount: fromEmails.length,
     });

+    const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
+    if (!inbox) {
+      log.warn("Inbox mailbox not found");
+      return;
+    }
+    const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);
+
     for (const sender of fromEmails) {
-      const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
-      if (!inbox) continue;
-
       // Query for all emails from this sender in inbox
       const response = await this.client.request([
         // ... rest of the query
       ]);

       // ... rest of the code, remove the archive lookup from inside
-      if (emailIds.length > 0) {
-        // Archive all emails
-        const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);

2670-2676: Assumption: first identity is default.

The code assumes index === 0 means default identity. JMAP identities don't have a standard "default" flag, so this heuristic is reasonable, but consider documenting this behavior.


326-351: Extract repeated email properties to a constant.

The same list of email properties is repeated in ~15 places throughout the file. Extract to a constant for maintainability.

🔎 Add at the top of the file:
const EMAIL_PROPERTIES = [
  "id",
  "threadId",
  "mailboxIds",
  "keywords",
  "from",
  "to",
  "cc",
  "bcc",
  "subject",
  "receivedAt",
  "sentAt",
  "preview",
  "hasAttachment",
  "messageId",
  "inReplyTo",
  "references",
  "replyTo",
  "bodyStructure",
  "bodyValues",
  "textBody",
  "htmlBody",
  "attachments",
] as const;

Then use properties: EMAIL_PROPERTIES in all Email/get calls.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4166d8c and b58f5f1.

📒 Files selected for processing (15)
  • apps/web/app/(app)/accounts/AddAccount.tsx (5 hunks)
  • apps/web/app/api/fastmail/linking/auth-url/route.ts (1 hunks)
  • apps/web/app/api/fastmail/linking/callback/route.ts (1 hunks)
  • apps/web/env.ts (1 hunks)
  • apps/web/utils/account-linking.ts (2 hunks)
  • apps/web/utils/account.ts (2 hunks)
  • apps/web/utils/auth.ts (2 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
  • apps/web/utils/email/provider-types.ts (1 hunks)
  • apps/web/utils/email/provider.ts (2 hunks)
  • apps/web/utils/email/types.ts (1 hunks)
  • apps/web/utils/fastmail/client.ts (1 hunks)
  • apps/web/utils/fastmail/constants.ts (1 hunks)
  • apps/web/utils/fastmail/scopes.ts (1 hunks)
  • apps/web/utils/oauth/account-linking.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (28)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/{server,api,actions,utils}/**/*.ts

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

**/{server,api,actions,utils}/**/*.ts: Use createScopedLogger from "@/utils/logger" for logging in backend code
Add the createScopedLogger instantiation at the top of the file with an appropriate scope name
Use .with() method to attach context variables only within specific functions, not on global loggers
For large functions with reused variables, use createScopedLogger().with() to attach context once and reuse the logger without passing variables repeatedly

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/env.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/env.ts

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

apps/web/env.ts: Add server-only environment variables to apps/web/env.ts under the server object with Zod schema validation
Add client-side environment variables to apps/web/env.ts under the client object with NEXT_PUBLIC_ prefix and Zod schema validation
Add client-side environment variables to apps/web/env.ts under the experimental__runtimeEnv object to enable runtime access

Files:

  • apps/web/env.ts
{.env.example,apps/web/env.ts}

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

Client-side environment variables must be prefixed with NEXT_PUBLIC_

Files:

  • apps/web/env.ts
apps/web/**/{.env.example,env.ts,turbo.json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/env.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Create GET API routes using withAuth or withEmailAccount middleware in apps/web/app/api/*/route.ts, export response types as GetExampleResponse type alias for client-side type safety
Always export response types from GET routes as Get[Feature]Response using type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions with next-safe-action instead

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
**/app/**/route.ts

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

**/app/**/route.ts: Always wrap GET API route handlers with withAuth or withEmailAccount middleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes using Awaited<ReturnType<typeof functionName>> pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses using NextResponse.json() in GET API routes
Do not use try/catch blocks in GET API route handlers when using withAuth or withEmailAccount middleware, as the middleware handles error handling

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
apps/web/app/**/[!.]*/route.{ts,tsx}

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

Use kebab-case for route directories in Next.js App Router (e.g., api/hello-world/route)

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
apps/web/app/api/**/*.{ts,tsx}

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

apps/web/app/api/**/*.{ts,tsx}: API routes must use withAuth, withEmailAccount, or withError middleware for authentication
All database queries must include user scoping with emailAccountId or userId filtering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throw SafeError instead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields using select in database queries to prevent unintended information disclosure
Cron endpoints must use hasCronSecret or hasPostCronSecret to validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
**/app/api/**/*.ts

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

**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: use withEmailAccount for email-scoped operations, use withAuth for user-scoped operations, or use withError with proper validation for public/custom auth endpoints
Use withEmailAccount middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using emailAccountId
Use withAuth middleware for user-level operations such as user settings, API keys, and referrals that use only userId
Use withError middleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST use hasCronSecret() or hasPostCronSecret() validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret using hasCronSecret(request) or hasPostCronSecret(request) and capture unauthorized attempts with captureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/app/api/**/route.ts

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

apps/web/**/app/api/**/route.ts: Wrap GET API routes with withAuth or withEmailAccount middleware
Export response types from GET API routes for type-safe client use (e.g., GetExampleResponse)

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
apps/web/**/{app/api,utils/actions}/**/*.ts

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

apps/web/**/{app/api,utils/actions}/**/*.ts: Use server actions for all mutations (create/update/delete operations) instead of POST API routes
Use withAuth for user-level operations and withEmailAccount for email-account-level operations

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
apps/web/app/(app)/**/*.{ts,tsx}

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

apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

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

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
🧠 Learnings (41)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/account.ts
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/account.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/account-linking.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/account.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/account-linking.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/account.ts
  • apps/web/utils/email/provider.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/auth.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add server-only environment variables to `apps/web/env.ts` under the `server` object with Zod schema validation

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `experimental__runtimeEnv` object to enable runtime access

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{.env.example,env.ts,turbo.json} : Add environment variables to `.env.example`, `env.ts`, and `turbo.json`

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Prefix client-side environment variables with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to turbo.json : Add new environment variables to `turbo.json` under `tasks.build.env` as a global dependency for the build task

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to turbo.json : Add environment variables to `turbo.json` under `tasks.build.env` array to declare build-time dependencies

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{app/api,utils/actions}/**/*.ts : Use `withAuth` for user-level operations and `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/utils/account.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client

Applied to files:

  • apps/web/utils/account.ts
  • apps/web/utils/email/provider.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers

Applied to files:

  • apps/web/utils/account.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/account.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/auth.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules

Applied to files:

  • apps/web/utils/account.ts
  • apps/web/utils/email/provider.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/account.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests

Applied to files:

  • apps/web/utils/account.ts
📚 Learning: 2025-11-25T14:42:16.602Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:16.602Z
Learning: The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests

Applied to files:

  • apps/web/utils/account.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/{pages,routes,components}/**/*.{ts,tsx} : Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder

Applied to files:

  • apps/web/utils/account.ts
📚 Learning: 2025-11-25T14:38:08.183Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/logging.mdc:0-0
Timestamp: 2025-11-25T14:38:08.183Z
Learning: Applies to **/{server,api,actions,utils}/**/*.ts : Add the `createScopedLogger` instantiation at the top of the file with an appropriate scope name

Applied to files:

  • apps/web/utils/fastmail/scopes.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`

Applied to files:

  • apps/web/utils/fastmail/scopes.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: use `withEmailAccount` for email-scoped operations, use `withAuth` for user-scoped operations, or use `withError` with proper validation for public/custom auth endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Wrap GET API routes with `withAuth` or `withEmailAccount` middleware

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: `withEmailAccount` for email-scoped operations, `withAuth` for user-scoped operations, or `withError` with proper validation for public/cron endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Do not use try/catch blocks in GET API route handlers when using `withAuth` or `withEmailAccount` middleware, as the middleware handles error handling

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Infer and export the response type for GET API routes using `export type GetResponse = Awaited<ReturnType<typeof getData>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Always export response types from GET routes as `Get[Feature]Response` using type inference from the data fetching function for type-safe client consumption

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Export response types from GET API routes for type-safe client use (e.g., `GetExampleResponse`)

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Infer and export response type for GET API routes using `Awaited<ReturnType<typeof functionName>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (6)
apps/web/utils/account.ts (1)
apps/web/utils/fastmail/client.ts (1)
  • getFastmailClientWithRefresh (148-213)
apps/web/app/api/fastmail/linking/auth-url/route.ts (5)
apps/web/utils/fastmail/client.ts (2)
  • getLinkingOAuth2Config (62-71)
  • FASTMAIL_OAUTH_AUTHORIZE_URL (12-13)
apps/web/utils/oauth/state.ts (2)
  • generateOAuthState (10-18)
  • oauthStateCookieOptions (34-40)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (3-10)
apps/web/utils/middleware.ts (1)
  • withAuth (378-386)
apps/web/utils/fastmail/constants.ts (1)
  • FASTMAIL_LINKING_STATE_COOKIE_NAME (2-2)
apps/web/utils/fastmail/client.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/env.ts (1)
  • env (17-257)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (3-10)
apps/web/utils/error.ts (1)
  • SafeError (58-68)
apps/web/utils/auth.ts (1)
  • saveTokens (456-530)
apps/web/utils/auth.ts (1)
apps/web/utils/email/provider-types.ts (1)
  • isFastmailProvider (9-11)
apps/web/utils/account-linking.ts (2)
apps/web/app/api/fastmail/linking/auth-url/route.ts (1)
  • GetAuthLinkUrlResponse (14-14)
apps/web/app/api/outlook/linking/auth-url/route.ts (1)
  • GetOutlookAuthLinkUrlResponse (10-10)
apps/web/utils/email/fastmail.ts (7)
apps/web/utils/fastmail/client.ts (3)
  • JMAPMethodResponse (52-52)
  • FastmailClient (54-60)
  • getAccessTokenFromClient (236-238)
apps/web/utils/email/types.ts (4)
  • EmailThread (7-12)
  • EmailLabel (14-25)
  • EmailFilter (27-36)
  • EmailSignature (38-43)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (5-14)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (12-17)
⏰ 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: cubic · AI code reviewer
  • GitHub Check: Review for correctness

Comment on lines +62 to +71
export function getLinkingOAuth2Config() {
return {
clientId: env.FASTMAIL_CLIENT_ID || "",
clientSecret: env.FASTMAIL_CLIENT_SECRET || "",
redirectUri: `${env.NEXT_PUBLIC_BASE_URL}/api/fastmail/linking/callback`,
authorizeUrl: FASTMAIL_OAUTH_AUTHORIZE_URL,
tokenUrl: FASTMAIL_OAUTH_TOKEN_URL,
scopes: SCOPES,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation for required environment variables.

getLinkingOAuth2Config silently returns empty strings when FASTMAIL_CLIENT_ID or FASTMAIL_CLIENT_SECRET are not set. This will lead to obscure OAuth failures downstream rather than a clear error at configuration time.

🔎 Consider adding validation:
 export function getLinkingOAuth2Config() {
+  if (!env.FASTMAIL_CLIENT_ID || !env.FASTMAIL_CLIENT_SECRET) {
+    throw new SafeError("Fastmail OAuth credentials not configured");
+  }
+
   return {
-    clientId: env.FASTMAIL_CLIENT_ID || "",
-    clientSecret: env.FASTMAIL_CLIENT_SECRET || "",
+    clientId: env.FASTMAIL_CLIENT_ID,
+    clientSecret: env.FASTMAIL_CLIENT_SECRET,
     redirectUri: `${env.NEXT_PUBLIC_BASE_URL}/api/fastmail/linking/callback`,
     authorizeUrl: FASTMAIL_OAUTH_AUTHORIZE_URL,
     tokenUrl: FASTMAIL_OAUTH_TOKEN_URL,
     scopes: SCOPES,
   };
 }
🤖 Prompt for AI Agents
In apps/web/utils/fastmail/client.ts around lines 62 to 71,
getLinkingOAuth2Config currently returns empty strings when FASTMAIL_CLIENT_ID
or FASTMAIL_CLIENT_SECRET are unset; add explicit validation at the start of
this function to check env.FASTMAIL_CLIENT_ID and env.FASTMAIL_CLIENT_SECRET
(and optionally env.NEXT_PUBLIC_BASE_URL), and throw a clear Error with a
descriptive message if any required variable is missing so the app fails fast
and surfaces a helpful message during startup/configuration rather than
producing obscure OAuth failures downstream.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

4 issues found across 15 files

Prompt for AI agents (all 4 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/utils/fastmail/client.ts">

<violation number="1" location="apps/web/utils/fastmail/client.ts:165">
P2: Access token expiry check uses seconds value against `Date.now()` milliseconds, causing valid tokens to be treated as expired and forcing needless refreshes.</violation>
</file>

<file name="apps/web/utils/email/fastmail.ts">

<violation number="1" location="apps/web/utils/email/fastmail.ts:2555">
P1: Async callback inside `some()` will not work correctly. `Array.prototype.some()` doesn&#39;t handle Promises - async functions always return a truthy Promise object, causing this to return `true` whenever `labelIds` has any elements. The method should be made async and use a proper loop or `Promise.all` pattern.</violation>
</file>

<file name="apps/web/app/api/fastmail/linking/callback/route.ts">

<violation number="1" location="apps/web/app/api/fastmail/linking/callback/route.ts:24">
P2: Missing environment variable validation at the start of the handler. Unlike the Outlook callback which checks for `MICROSOFT_CLIENT_ID` and `MICROSOFT_CLIENT_SECRET` before proceeding, this handler doesn&#39;t validate that `FASTMAIL_CLIENT_ID` and `FASTMAIL_CLIENT_SECRET` are configured. If these aren&#39;t set, the request will fail with an unclear error from Fastmail&#39;s API.</violation>
</file>

<file name="apps/web/app/api/fastmail/linking/auth-url/route.ts">

<violation number="1" location="apps/web/app/api/fastmail/linking/auth-url/route.ts:28">
P1: `access_type: &quot;offline&quot;` is a Google-specific OAuth parameter. Fastmail uses OIDC where you need to add `offline_access` to the scope to get a refresh token. Without this, the token exchange may not return a refresh token.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

}

// Check if token is still valid
if (accessToken && expiresAt && expiresAt > Date.now()) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Access token expiry check uses seconds value against Date.now() milliseconds, causing valid tokens to be treated as expired and forcing needless refreshes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/fastmail/client.ts, line 165:

<comment>Access token expiry check uses seconds value against `Date.now()` milliseconds, causing valid tokens to be treated as expired and forcing needless refreshes.</comment>

<file context>
@@ -0,0 +1,238 @@
+  }
+
+  // Check if token is still valid
+  if (accessToken &amp;&amp; expiresAt &amp;&amp; expiresAt &gt; Date.now()) {
+    return createFastmailClient(accessToken);
+  }
</file context>

✅ Addressed in d3bd77c

// Check if the message is in the sent mailbox
// This would need to be checked against actual mailbox IDs
return (
message.labelIds?.some(async (id) => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P1: Async callback inside some() will not work correctly. Array.prototype.some() doesn't handle Promises - async functions always return a truthy Promise object, causing this to return true whenever labelIds has any elements. The method should be made async and use a proper loop or Promise.all pattern.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 2555:

<comment>Async callback inside `some()` will not work correctly. `Array.prototype.some()` doesn&#39;t handle Promises - async functions always return a truthy Promise object, causing this to return `true` whenever `labelIds` has any elements. The method should be made async and use a proper loop or `Promise.all` pattern.</comment>

<file context>
@@ -0,0 +1,2678 @@
+    // Check if the message is in the sent mailbox
+    // This would need to be checked against actual mailbox IDs
+    return (
+      message.labelIds?.some(async (id) =&gt; {
+        const mailbox = await this.getMailboxById(id);
+        return mailbox?.role === FastmailMailbox.SENT;
</file context>

✅ Addressed in d3bd77c

import { isDuplicateError } from "@/utils/prisma-helpers";

export const GET = withError("fastmail/linking/callback", async (request) => {
const logger = request.logger;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Missing environment variable validation at the start of the handler. Unlike the Outlook callback which checks for MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET before proceeding, this handler doesn't validate that FASTMAIL_CLIENT_ID and FASTMAIL_CLIENT_SECRET are configured. If these aren't set, the request will fail with an unclear error from Fastmail's API.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/fastmail/linking/callback/route.ts, line 24:

<comment>Missing environment variable validation at the start of the handler. Unlike the Outlook callback which checks for `MICROSOFT_CLIENT_ID` and `MICROSOFT_CLIENT_SECRET` before proceeding, this handler doesn&#39;t validate that `FASTMAIL_CLIENT_ID` and `FASTMAIL_CLIENT_SECRET` are configured. If these aren&#39;t set, the request will fail with an unclear error from Fastmail&#39;s API.</comment>

<file context>
@@ -0,0 +1,303 @@
+import { isDuplicateError } from &quot;@/utils/prisma-helpers&quot;;
+
+export const GET = withError(&quot;fastmail/linking/callback&quot;, async (request) =&gt; {
+  const logger = request.logger;
+
+  const searchParams = request.nextUrl.searchParams;
</file context>

✅ Addressed in d3bd77c

scope: [...new Set([...SCOPES, "openid", "email", "profile"])].join(" "),
state,
// Request offline access to get refresh token
access_type: "offline",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P1: access_type: "offline" is a Google-specific OAuth parameter. Fastmail uses OIDC where you need to add offline_access to the scope to get a refresh token. Without this, the token exchange may not return a refresh token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/fastmail/linking/auth-url/route.ts, line 28:

<comment>`access_type: &quot;offline&quot;` is a Google-specific OAuth parameter. Fastmail uses OIDC where you need to add `offline_access` to the scope to get a refresh token. Without this, the token exchange may not return a refresh token.</comment>

<file context>
@@ -0,0 +1,50 @@
+    scope: [...new Set([...SCOPES, &quot;openid&quot;, &quot;email&quot;, &quot;profile&quot;])].join(&quot; &quot;),
+    state,
+    // Request offline access to get refresh token
+    access_type: &quot;offline&quot;,
+    prompt: &quot;consent&quot;,
+  });
</file context>

✅ Addressed in d3bd77c

@elie222
Copy link
Owner

elie222 commented Dec 18, 2025

Have you tested it? I'd love to know if this works well

@marcodejongh
Copy link
Author

@elie222 not yet,, but Ill have a go at deploying it either today or on the weekend. I use Fastmail myself, so very motivated to get it working 😆.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/web/utils/fastmail/client.ts (3)

216-225: Missing validation for required environment variables.

getLinkingOAuth2Config silently returns empty strings when FASTMAIL_CLIENT_ID or FASTMAIL_CLIENT_SECRET are not set. While the calling code in the auth-url route now validates this, it would be safer to fail fast here.


241-244: Missing await on response.json().

The function returns a Promise chain rather than awaiting the JSON parsing result. This is inconsistent with the pattern used elsewhere in this file and could mask parsing errors.

🔎 Apply this diff:
-  return response.json();
+  return await response.json();

437-440: Missing await on response.json() in getUserInfo.

Same inconsistency as getJMAPSession - the function should await the JSON parsing.

🔎 Apply this diff:
-  return response.json();
+  return await response.json();
🧹 Nitpick comments (1)
apps/web/utils/email/fastmail.ts (1)

597-642: Duplicate property list that could use the shared constant.

The getMessage method defines its own property list instead of using the EMAIL_PROPERTIES constant defined at the top of the file.

🔎 Consider using the shared constant:
         properties: [
-            "id",
-            "threadId",
-            "mailboxIds",
-            ...
-            "attachments",
-          ],
+            ...EMAIL_PROPERTIES,
+         ],
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3bd77c and 3f842dd.

📒 Files selected for processing (4)
  • apps/web/app/api/fastmail/linking/auth-url/route.ts (1 hunks)
  • apps/web/app/api/fastmail/linking/callback/route.ts (1 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
  • apps/web/utils/fastmail/client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Create GET API routes using withAuth or withEmailAccount middleware in apps/web/app/api/*/route.ts, export response types as GetExampleResponse type alias for client-side type safety
Always export response types from GET routes as Get[Feature]Response using type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions with next-safe-action instead

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/**/route.ts

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

**/app/**/route.ts: Always wrap GET API route handlers with withAuth or withEmailAccount middleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes using Awaited<ReturnType<typeof functionName>> pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses using NextResponse.json() in GET API routes
Do not use try/catch blocks in GET API route handlers when using withAuth or withEmailAccount middleware, as the middleware handles error handling

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/**/[!.]*/route.{ts,tsx}

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

Use kebab-case for route directories in Next.js App Router (e.g., api/hello-world/route)

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/api/**/*.{ts,tsx}

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

apps/web/app/api/**/*.{ts,tsx}: API routes must use withAuth, withEmailAccount, or withError middleware for authentication
All database queries must include user scoping with emailAccountId or userId filtering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throw SafeError instead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields using select in database queries to prevent unintended information disclosure
Cron endpoints must use hasCronSecret or hasPostCronSecret to validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/api/**/*.ts

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

**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: use withEmailAccount for email-scoped operations, use withAuth for user-scoped operations, or use withError with proper validation for public/custom auth endpoints
Use withEmailAccount middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using emailAccountId
Use withAuth middleware for user-level operations such as user settings, API keys, and referrals that use only userId
Use withError middleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST use hasCronSecret() or hasPostCronSecret() validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret using hasCronSecret(request) or hasPostCronSecret(request) and capture unauthorized attempts with captureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/app/api/**/route.ts

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

apps/web/**/app/api/**/route.ts: Wrap GET API routes with withAuth or withEmailAccount middleware
Export response types from GET API routes for type-safe client use (e.g., GetExampleResponse)

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/{app/api,utils/actions}/**/*.ts

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

apps/web/**/{app/api,utils/actions}/**/*.ts: Use server actions for all mutations (create/update/delete operations) instead of POST API routes
Use withAuth for user-level operations and withEmailAccount for email-account-level operations

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/{app,pages}/**/{route,+page}.{ts,tsx}

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

**/{app,pages}/**/{route,+page}.{ts,tsx}: Use middleware wrappers (withError, withAuth, withEmailAccount, withEmailProvider) that automatically create loggers with request context in API routes
Enrich logger context within route handlers using logger.with() to add request-specific fields like messageId

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
🧠 Learnings (31)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Wrap GET API routes with `withAuth` or `withEmailAccount` middleware

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Infer and export the response type for GET API routes using `export type GetResponse = Awaited<ReturnType<typeof getData>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Always export response types from GET routes as `Get[Feature]Response` using type inference from the data fetching function for type-safe client consumption

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Infer and export response type for GET API routes using `Awaited<ReturnType<typeof functionName>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Export response types from GET API routes for type-safe client use (e.g., `GetExampleResponse`)

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: use `withEmailAccount` for email-scoped operations, use `withAuth` for user-scoped operations, or use `withError` with proper validation for public/custom auth endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: `withEmailAccount` for email-scoped operations, `withAuth` for user-scoped operations, or `withError` with proper validation for public/cron endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Do not use try/catch blocks in GET API route handlers when using `withAuth` or `withEmailAccount` middleware, as the middleware handles error handling

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't hardcode sensitive data like API keys and tokens

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Always validate that resources belong to the authenticated user before any operation - use ownership checks in queries (e.g., `emailAccount: { id: emailAccountId }`) and throw `SafeError` if validation fails

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (3)
apps/web/app/api/fastmail/linking/auth-url/route.ts (6)
apps/web/utils/fastmail/client.ts (2)
  • getLinkingOAuth2Config (216-225)
  • FASTMAIL_OAUTH_AUTHORIZE_URL (92-93)
apps/web/utils/oauth/state.ts (2)
  • generateOAuthState (10-18)
  • oauthStateCookieOptions (34-40)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (8-17)
apps/web/app/api/fastmail/linking/callback/route.ts (1)
  • GET (24-323)
apps/web/utils/middleware.ts (1)
  • withAuth (378-386)
apps/web/utils/fastmail/constants.ts (1)
  • FASTMAIL_LINKING_STATE_COOKIE_NAME (10-10)
apps/web/utils/fastmail/client.ts (3)
apps/web/utils/error.ts (1)
  • SafeError (58-68)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (8-17)
apps/web/utils/auth.ts (1)
  • saveTokens (456-530)
apps/web/utils/email/fastmail.ts (8)
apps/web/utils/fastmail/client.ts (1)
  • FastmailClient (199-210)
apps/web/utils/email/types.ts (5)
  • EmailProvider (45-245)
  • EmailThread (7-12)
  • EmailLabel (14-25)
  • EmailFilter (27-36)
  • EmailSignature (38-43)
apps/web/utils/logger.ts (2)
  • Logger (5-5)
  • createScopedLogger (17-81)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (22-39)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (12-17)
🔇 Additional comments (22)
apps/web/utils/fastmail/client.ts (4)

1-13: LGTM! Well-structured imports and module setup.

The file establishes a clean foundation for the Fastmail JMAP client with appropriate dependencies for authentication, error handling, and logging.


15-80: Well-implemented retry logic with exponential backoff.

The fetchWithRetry function properly handles transient errors (429, 502, 503, 504), respects Retry-After headers, and uses exponential backoff. The implementation correctly distinguishes between HTTP errors and network errors.


351-363: Good handling of token expiry timestamp formats.

The code properly handles both Unix timestamp (seconds) and Date timestamp (milliseconds) formats for expiresAt, with a reasonable threshold check. This addresses the previously reported issue.


292-323: Solid client creation with capability validation.

The createFastmailClient function properly validates required JMAP capabilities and extracts the primary mail account ID before returning the client instance.

apps/web/utils/email/fastmail.ts (9)

1-54: Well-organized imports and constants.

The file properly imports types and utilities, and defines a reusable EMAIL_PROPERTIES constant to ensure consistency across JMAP requests.


202-228: Clean provider class initialization.

The FastmailProvider class properly implements the EmailProvider interface with appropriate logger initialization and instance variables for caching.


983-1004: Correct use of JSON Pointer notation for mailbox patching.

The archiveThread method now correctly uses JSON Pointer notation (mailboxIds/${inbox.id}) to patch individual mailbox memberships instead of replacing the entire mailboxIds object. This addresses the previously reported bug.


1039-1062: Consistent JSON Pointer pattern in archiveMessage.

Same correct pattern applied for single message archiving.


1606-1649: Attachment upload now implemented correctly.

The sendEmailWithHtml method properly uploads attachments via the blob upload mechanism and includes them in the email creation request. This addresses the previously reported issue about unused attachments parameter.


2754-2769: Correct synchronous implementation of isSentMessage.

The method now correctly uses the synchronous mailbox cache lookup instead of the broken async callback pattern. This addresses the previously reported critical bug.


1163-1169: bulkTrashFromSenders only adds to trash without removing from other mailboxes.

The trash operation adds messages to the Trash mailbox but doesn't remove them from other mailboxes (like Inbox). This might not match the expected "move to trash" behavior. Consider whether this is intentional (keeping in both) or if you should also remove from the source mailbox.


1218-1224: Same concern in trashThread - only adds to trash.

Similar to bulkTrashFromSenders, this only adds the trash mailbox flag without removing from other mailboxes.


2720-2733: Appropriate stub implementations for unsupported features.

The processHistory, watchEmails, and unwatchEmails methods correctly log warnings about unsupported functionality. JMAP uses EventSource for real-time updates, which would require a different architecture.

apps/web/app/api/fastmail/linking/auth-url/route.ts (3)

1-14: Well-structured imports and type export.

The file properly imports dependencies and exports the GetAuthLinkUrlResponse type for client-side type safety, following the coding guidelines.


16-36: Correct OAuth URL construction with OIDC-standard scopes.

The code now correctly uses the offline_access scope (OIDC standard) instead of the Google-specific access_type: "offline" parameter. The use of Set to deduplicate scopes is a nice touch.


38-60: Proper configuration validation and state management.

The route correctly:

  1. Validates OAuth credentials before proceeding
  2. Uses withAuth middleware as required by coding guidelines
  3. Sets the state cookie with proper options for CSRF protection
apps/web/app/api/fastmail/linking/callback/route.ts (6)

1-22: Comprehensive imports for OAuth callback handling.

The file imports all necessary utilities for OAuth validation, account linking, error handling, and race condition management.


24-36: Good: Environment variable validation added.

The handler now validates FASTMAIL_CLIENT_ID and FASTMAIL_CLIENT_SECRET at the start and returns a user-friendly error redirect if not configured. This addresses the previously reported issue.


57-80: Robust OAuth code handling with caching and locking.

The implementation properly:

  1. Checks for cached results to avoid reprocessing
  2. Acquires a distributed lock to prevent race conditions
  3. Handles the case where another request is already processing the code

82-116: Proper token exchange with error handling.

The code exchange follows OAuth best practices with proper error logging and exception capture.


164-237: Well-handled account creation with race condition recovery.

The continue_create branch properly handles the case where a concurrent request may have already created the account, avoiding duplicate errors.


312-322: Proper cleanup in error handler.

The error handler correctly clears the OAuth code before redirecting to prevent replay attacks.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

3 issues found across 17 files

Prompt for AI agents (all 3 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/utils/fastmail/client.ts">

<violation number="1" location="apps/web/utils/fastmail/client.ts:47">
P2: `Retry-After` header can be an HTTP-date string per RFC 7231. When it is, `Number.parseInt` returns `NaN`, causing `setTimeout` to execute immediately (0ms delay). Consider validating the parsed value or checking if it&#39;s a date string.</violation>
</file>

<file name="apps/web/app/api/fastmail/linking/callback/route.ts">

<violation number="1" location="apps/web/app/api/fastmail/linking/callback/route.ts:113">
P2: Missing validation for refresh_token allows creating/updating Fastmail accounts without a refresh token, leading to inevitable token expiry and a broken account.</violation>
</file>

<file name="apps/web/utils/email/fastmail.ts">

<violation number="1" location="apps/web/utils/email/fastmail.ts:1782">
P1: Setting `keywords: { $seen: read }` replaces the entire keywords object, which will remove other email flags like `$flagged` (starred) and `$draft`. Use JSON Pointer notation to patch only the `$seen` keyword while preserving other keywords.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

// Check for Retry-After header
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? Number.parseInt(retryAfter, 10) * 1000
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Retry-After header can be an HTTP-date string per RFC 7231. When it is, Number.parseInt returns NaN, causing setTimeout to execute immediately (0ms delay). Consider validating the parsed value or checking if it's a date string.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/fastmail/client.ts, line 47:

<comment>`Retry-After` header can be an HTTP-date string per RFC 7231. When it is, `Number.parseInt` returns `NaN`, causing `setTimeout` to execute immediately (0ms delay). Consider validating the parsed value or checking if it&#39;s a date string.</comment>

<file context>
@@ -0,0 +1,449 @@
+      // Check for Retry-After header
+      const retryAfter = response.headers.get(&quot;Retry-After&quot;);
+      const delay = retryAfter
+        ? Number.parseInt(retryAfter, 10) * 1000
+        : BASE_RETRY_DELAY * 2 ** attempt;
+
</file context>

✅ Addressed in b0819eb


const tokens = await tokenResponse.json();

if (!tokens.access_token) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Missing validation for refresh_token allows creating/updating Fastmail accounts without a refresh token, leading to inevitable token expiry and a broken account.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/fastmail/linking/callback/route.ts, line 113:

<comment>Missing validation for refresh_token allows creating/updating Fastmail accounts without a refresh token, leading to inevitable token expiry and a broken account.</comment>

<file context>
@@ -0,0 +1,323 @@
+
+    const tokens = await tokenResponse.json();
+
+    if (!tokens.access_token) {
+      throw new Error(&quot;Missing access_token from Fastmail response&quot;);
+    }
</file context>

✅ Addressed in b0819eb

if (emailIds.length === 0) return;

// Update $seen keyword
const update: Record<string, { keywords: Record<string, boolean> }> = {};
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P1: Setting keywords: { $seen: read } replaces the entire keywords object, which will remove other email flags like $flagged (starred) and $draft. Use JSON Pointer notation to patch only the $seen keyword while preserving other keywords.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 1782:

<comment>Setting `keywords: { $seen: read }` replaces the entire keywords object, which will remove other email flags like `$flagged` (starred) and `$draft`. Use JSON Pointer notation to patch only the `$seen` keyword while preserving other keywords.</comment>

<file context>
@@ -0,0 +1,2887 @@
+    if (emailIds.length === 0) return;
+
+    // Update $seen keyword
+    const update: Record&lt;string, { keywords: Record&lt;string, boolean&gt; }&gt; = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {
</file context>

✅ Addressed in b0819eb

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/web/utils/fastmail/client.ts (2)

216-225: Missing validation for required environment variables.

getLinkingOAuth2Config returns empty strings when FASTMAIL_CLIENT_ID or FASTMAIL_CLIENT_SECRET are not set, leading to obscure OAuth failures downstream rather than a clear error at configuration time.


44-48: Retry-After header parsing may fail for HTTP-date format.

Per RFC 7231, Retry-After can be either seconds or an HTTP-date string. When it's a date string, Number.parseInt returns NaN, causing setTimeout to execute immediately (0ms delay) instead of backing off.

🔎 Consider validating the parsed value
       const retryAfter = response.headers.get("Retry-After");
-      const delay = retryAfter
-        ? Number.parseInt(retryAfter, 10) * 1000
-        : BASE_RETRY_DELAY * 2 ** attempt;
+      let delay = BASE_RETRY_DELAY * 2 ** attempt;
+      if (retryAfter) {
+        const parsed = Number.parseInt(retryAfter, 10);
+        if (!Number.isNaN(parsed)) {
+          delay = parsed * 1000;
+        }
+        // Falls back to exponential backoff if Retry-After is HTTP-date or invalid
+      }
🧹 Nitpick comments (4)
apps/web/utils/actions/fastmail-app-token.validation.ts (1)

1-7: Schema follows project conventions.

The validation file correctly exports both the Zod schema and inferred type as per coding guidelines.

Consider adding a regex pattern for Fastmail app tokens (typically fmu1-xxxxxxxx-xxxxxxxxxxxxxxxxxxxx format) to catch invalid tokens earlier, though the server-side validation will catch them anyway.

apps/web/utils/actions/fastmail-app-token.ts (1)

98-99: Consider defining scopes as a constant.

The hardcoded scope string duplicates values from SCOPES in @/utils/fastmail/scopes.ts. Consider importing and joining the scopes array for consistency.

🔎 Suggested change
+import { SCOPES } from "@/utils/fastmail/scopes";
+
 // In the account creation:
 scope:
-  "urn:ietf:params:jmap:core urn:ietf:params:jmap:mail urn:ietf:params:jmap:submission",
+  SCOPES.slice(0, 3).join(" "), // core, mail, submission
apps/web/utils/fastmail/client.ts (2)

243-243: Missing await on response.json() return statements.

Lines 243 and 445 return response.json() without await. While this works because the function is async and the caller awaits, it's inconsistent with the pattern at line 278 and can mask JSON parsing errors at the call site.

🔎 Apply these changes for consistency
 // Line 243
-  return response.json();
+  return await response.json();

 // Line 445
-  return response.json();
+  return await response.json();

Also applies to: 445-445


396-418: Consider validating token response before use.

If the token endpoint returns an unexpected response format, tokens.access_token could be undefined, which would then be passed to createFastmailClient. Adding a validation check would make this more robust.

🔎 Add validation
   const tokens = await response.json();
   const newAccessToken = tokens.access_token;
+  
+  if (!newAccessToken) {
+    logger.error("Token refresh response missing access_token", { emailAccountId });
+    throw new SafeError("Failed to refresh Fastmail token: invalid response");
+  }
+  
   const newRefreshToken = tokens.refresh_token;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f842dd and 68a85b2.

📒 Files selected for processing (5)
  • apps/web/app/(app)/accounts/AddAccount.tsx (5 hunks)
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (1 hunks)
  • apps/web/utils/actions/fastmail-app-token.ts (1 hunks)
  • apps/web/utils/actions/fastmail-app-token.validation.ts (1 hunks)
  • apps/web/utils/fastmail/client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (25)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/utils/actions/*.ts

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

apps/web/utils/actions/*.ts: Use next-safe-action with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in apps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from the actionClient

apps/web/utils/actions/*.ts: Create corresponding server action implementation files using the naming convention apps/web/utils/actions/NAME.ts with 'use server' directive
Use 'use server' directive at the top of server action implementation files
Implement all server actions using the next-safe-action library with actionClient, actionClientUser, or adminActionClient for type safety and validation
Use actionClientUser when only authenticated user context (userId) is needed
Use actionClient when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
Use adminActionClient for actions restricted to admin users
Add metadata with a meaningful action name using .metadata({ name: "actionName" }) for Sentry instrumentation and monitoring
Use .schema() method with Zod validation schemas from corresponding .validation.ts files in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via the ctx object parameter in the .action() handler
Use revalidatePath or revalidateTag from 'next/cache' within server action handlers when mutations modify data displayed elsewhere

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/utils/actions/**/*.ts

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

apps/web/utils/actions/**/*.ts: Server actions must be located in apps/web/utils/actions folder
Server action files must start with use server directive

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/utils/actions/*.ts

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

apps/web/**/utils/actions/*.ts: Use next-safe-action with proper Zod validation for server actions
Call revalidatePath in server actions for cache invalidation after mutations

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/{app/api,utils/actions}/**/*.ts

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

apps/web/**/{app/api,utils/actions}/**/*.ts: Use server actions for all mutations (create/update/delete operations) instead of POST API routes
Use withAuth for user-level operations and withEmailAccount for email-account-level operations

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/app/(app)/**/*.{ts,tsx}

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

apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
**/*.tsx

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
**/*.validation.{ts,tsx}

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

**/*.validation.{ts,tsx}: Define validation schemas using Zod
Use descriptive error messages in validation schemas

Files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/utils/actions/*.validation.ts

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

apps/web/utils/actions/*.validation.ts: Define Zod validation schemas in separate *.validation.ts files and export both the schema and inferred type (e.g., CreateExampleBody)
Export types from Zod schemas using z.infer<> to maintain type safety between validation and client usage

apps/web/utils/actions/*.validation.ts: Create separate validation files for server actions using the naming convention apps/web/utils/actions/NAME.validation.ts containing Zod schemas and inferred types
Define input validation schemas using Zod in .validation.ts files and export both the schema and its inferred TypeScript type

Files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
apps/web/**/utils/actions/*.validation.ts

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

Create separate Zod validation schema files for server action inputs

Files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
🧠 Learnings (49)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Implement all server actions using the `next-safe-action` library with actionClient, actionClientUser, or adminActionClient for type safety and validation

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `next-safe-action` with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in `apps/web/utils/actions/*.validation.ts`

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.ts : Use `next-safe-action` with proper Zod validation for server actions

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.ts : Server actions should use 'use server' directive and automatically receive authentication context (`emailAccountId`) from the `actionClient`

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Create separate validation files for server actions using the naming convention `apps/web/utils/actions/NAME.validation.ts` containing Zod schemas and inferred types

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.validation.ts : Create separate Zod validation schema files for server action inputs

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Create corresponding server action implementation files using the naming convention `apps/web/utils/actions/NAME.ts` with 'use server' directive

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{app/api,utils/actions}/**/*.ts : Use `withAuth` for user-level operations and `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `.schema()` method with Zod validation schemas from corresponding `.validation.ts` files in next-safe-action configuration

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `revalidatePath` or `revalidateTag` from 'next/cache' within server action handlers when mutations modify data displayed elsewhere

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T02:38:37.011Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1103
File: apps/web/utils/actions/rule.ts:447-457
Timestamp: 2025-12-17T02:38:37.011Z
Learning: In apps/web/utils/actions/rule.ts, revalidatePath is not needed for toggleAllRulesAction because rules data is fetched client-side using SWR, not server-side. Server-side cache revalidation is only needed when using Next.js server components or server-side data fetching.

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.ts : Call `revalidatePath` in server actions for cache invalidation after mutations

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `adminActionClient` for actions restricted to admin users

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Handle form submission results using `result?.serverError` to show error toasts and `toastSuccess` to show success messages after server action completion

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Use React Hook Form with Zod validation (`zodResolver`) for form handling, with form components using `register`, `handleSubmit`, and error handling from the hook

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Validate form inputs before submission using React Hook Form and Zod resolver

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for form validation

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/*{Form,Form.tsx} : Use React Hook Form with Zod validation for form handling

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*Form.{ts,tsx} : Use React Hook Form with Zod for validation in form components

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` props

Applied to files:

  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't hardcode sensitive data like API keys and tokens

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Always validate that resources belong to the authenticated user before any operation - use ownership checks in queries (e.g., `emailAccount: { id: emailAccountId }`) and throw `SafeError` if validation fails

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define input validation schemas using Zod in `.validation.ts` files and export both the schema and its inferred TypeScript type

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod validation schemas in separate `*.validation.ts` files and export both the schema and inferred type (e.g., `CreateExampleBody`)

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Export types from Zod schemas using `z.infer<>` to maintain type safety between validation and client usage

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Define validation schemas using Zod

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : All input parameters must be validated - check for presence, type, and format before use; use Zod schemas to validate request bodies with type guards and constraints

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Use Zod schemas for request body validation in API routes

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.validation.ts
🧬 Code graph analysis (2)
apps/web/app/(app)/accounts/AddAccount.tsx (3)
apps/web/utils/llms/config.ts (1)
  • Provider (3-12)
apps/web/components/new-landing/common/Card.tsx (1)
  • CardContent (10-12)
apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (1)
  • FastmailAppTokenModal (27-97)
apps/web/utils/fastmail/client.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/utils/error.ts (1)
  • SafeError (58-68)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (8-17)
apps/web/utils/auth.ts (1)
  • saveTokens (456-530)
🔇 Additional comments (10)
apps/web/app/(app)/accounts/AddAccount.tsx (3)

13-19: Well-structured provider abstraction.

The Provider type and PROVIDER_DISPLAY_NAMES map cleanly support the new Fastmail provider alongside existing ones. This aligns with the project's goal of provider-agnostic design (per retrieved learnings).


27-36: Clean loading state management.

The isAnyLoading derived flag and setLoadingMap pattern effectively prevent concurrent account linking attempts while keeping the code DRY.


87-106: Good UX with dual Fastmail linking options.

Offering both OAuth flow via the main button and App Token fallback via the link provides flexibility for users whose Fastmail setup may not support standard OAuth.

apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (2)

31-44: Correct error handling pattern.

The onError callback properly extracts error.error.serverError with a fallback message, following the project's guidelines for mutation error handling with toastError.


70-78: Secure token input.

Using type="password" for the app token field is appropriate since it prevents shoulder-surfing and accidental exposure of the sensitive credential.

apps/web/utils/actions/fastmail-app-token.ts (2)

16-29: Proper token validation with SafeError.

The action correctly validates the app token by attempting to create a JMAP client, and uses SafeError to return a user-friendly message without exposing internal details. This follows security guidelines.


88-120: Robust account creation with race condition handling.

The duplicate error check (lines 121-146) handles the edge case where concurrent requests might create the same account, returning success if the current user owns the newly created account. This is defensive programming.

apps/web/utils/fastmail/client.ts (3)

15-80: Well-implemented retry logic with exponential backoff.

The fetchWithRetry function properly handles transient errors (429, 502, 503, 504) with configurable retries and exponential backoff. The logging provides good observability for debugging retry scenarios.


357-369: Robust handling of timestamp format ambiguity.

The heuristic at lines 362-365 correctly handles both Unix timestamps in seconds and milliseconds by checking if the value is less than year 3000 in seconds. The comment explains the rationale well.


292-323: Clean client factory with capability validation.

The createFastmailClient function properly validates required JMAP capabilities before returning the client, ensuring downstream code won't fail due to missing capabilities.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

5 issues found across 20 files

Prompt for AI agents (all 5 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/utils/fastmail/client.ts">

<violation number="1" location="apps/web/utils/fastmail/client.ts:47">
P2: Retry-After header parsing doesn&#39;t handle HTTP-date format. If the server returns a date string instead of seconds, `parseInt` returns NaN causing immediate retries (NaN becomes 0 in setTimeout). The existing Outlook retry utility handles both formats properly.</violation>
</file>

<file name="apps/web/utils/email/fastmail.ts">

<violation number="1" location="apps/web/utils/email/fastmail.ts:1355">
P2: After extracting the email address, the recipient&#39;s display name is discarded. The JMAP `to` field supports both `email` and `name` properties. Consider preserving the sender&#39;s name for better email formatting.</violation>

<violation number="2" location="apps/web/utils/email/fastmail.ts:1410">
P1: The `to` field uses the full &quot;Name &lt;email&gt;&quot; format string from `email.headers.from` instead of extracting just the email address. JMAP&#39;s email field expects only the email address, which will cause delivery failures when the original sender has a display name.</violation>

<violation number="3" location="apps/web/utils/email/fastmail.ts:1782">
P2: Marking a thread as read overwrites the entire keywords map, clearing flags like $flagged/$draft. Patch only $seen instead of replacing all keywords.</violation>

<violation number="4" location="apps/web/utils/email/fastmail.ts:2472">
P2: String concatenation in a loop is O(n²) and will cause severe performance issues for large attachments. Consider using `Buffer.from(buffer).toString(&#39;base64&#39;)` in Node.js 22+ for efficient base64 encoding.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

// Check for Retry-After header
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? Number.parseInt(retryAfter, 10) * 1000
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Retry-After header parsing doesn't handle HTTP-date format. If the server returns a date string instead of seconds, parseInt returns NaN causing immediate retries (NaN becomes 0 in setTimeout). The existing Outlook retry utility handles both formats properly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/fastmail/client.ts, line 47:

<comment>Retry-After header parsing doesn&#39;t handle HTTP-date format. If the server returns a date string instead of seconds, `parseInt` returns NaN causing immediate retries (NaN becomes 0 in setTimeout). The existing Outlook retry utility handles both formats properly.</comment>

<file context>
@@ -0,0 +1,455 @@
+      // Check for Retry-After header
+      const retryAfter = response.headers.get(&quot;Retry-After&quot;);
+      const delay = retryAfter
+        ? Number.parseInt(retryAfter, 10) * 1000
+        : BASE_RETRY_DELAY * 2 ** attempt;
+
</file context>

✅ Addressed in b0819eb

if (emailIds.length === 0) return;

// Update $seen keyword
const update: Record<string, { keywords: Record<string, boolean> }> = {};
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: Marking a thread as read overwrites the entire keywords map, clearing flags like $flagged/$draft. Patch only $seen instead of replacing all keywords.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 1782:

<comment>Marking a thread as read overwrites the entire keywords map, clearing flags like $flagged/$draft. Patch only $seen instead of replacing all keywords.</comment>

<file context>
@@ -0,0 +1,2887 @@
+    if (emailIds.length === 0) return;
+
+    // Update $seen keyword
+    const update: Record&lt;string, { keywords: Record&lt;string, boolean&gt; }&gt; = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {
</file context>

✅ Addressed in b0819eb

mailboxIds: { [drafts.id]: true },
keywords: { $draft: true },
from: [{ email: userEmail }],
to: [{ email: to }],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: After extracting the email address, the recipient's display name is discarded. The JMAP to field supports both email and name properties. Consider preserving the sender's name for better email formatting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 1355:

<comment>After extracting the email address, the recipient&#39;s display name is discarded. The JMAP `to` field supports both `email` and `name` properties. Consider preserving the sender&#39;s name for better email formatting.</comment>

<file context>
@@ -0,0 +1,2887 @@
+              mailboxIds: { [drafts.id]: true },
+              keywords: { $draft: true },
+              from: [{ email: userEmail }],
+              to: [{ email: to }],
+              subject,
+              bodyValues: {
</file context>

✅ Addressed in 9fb10b9

const buffer = await response.arrayBuffer();
// Convert ArrayBuffer to base64 using Web APIs
const bytes = new Uint8Array(buffer);
let binary = "";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P2: String concatenation in a loop is O(n²) and will cause severe performance issues for large attachments. Consider using Buffer.from(buffer).toString('base64') in Node.js 22+ for efficient base64 encoding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 2472:

<comment>String concatenation in a loop is O(n²) and will cause severe performance issues for large attachments. Consider using `Buffer.from(buffer).toString(&#39;base64&#39;)` in Node.js 22+ for efficient base64 encoding.</comment>

<file context>
@@ -0,0 +1,2887 @@
+    const buffer = await response.arrayBuffer();
+    // Convert ArrayBuffer to base64 using Web APIs
+    const bytes = new Uint8Array(buffer);
+    let binary = &quot;&quot;;
+    for (let i = 0; i &lt; bytes.byteLength; i++) {
+      binary += String.fromCharCode(bytes[i]);
</file context>

✅ Addressed in 9fb10b9

throw new Error("No identity found for sending");
}

const to = email.headers.from;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 18, 2025

Choose a reason for hiding this comment

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

P1: The to field uses the full "Name " format string from email.headers.from instead of extracting just the email address. JMAP's email field expects only the email address, which will cause delivery failures when the original sender has a display name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/email/fastmail.ts, line 1410:

<comment>The `to` field uses the full &quot;Name &lt;email&gt;&quot; format string from `email.headers.from` instead of extracting just the email address. JMAP&#39;s email field expects only the email address, which will cause delivery failures when the original sender has a display name.</comment>

<file context>
@@ -0,0 +1,2887 @@
+      throw new Error(&quot;No identity found for sending&quot;);
+    }
+
+    const to = email.headers.from;
+
+    await this.client.request([
</file context>

✅ Addressed in 9fb10b9

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
apps/web/utils/fastmail/client.ts (2)

251-254: Missing await on response.json() return.

The function returns response.json() without awaiting. While this works because the caller awaits the async function result, it's inconsistent with the pattern elsewhere and could mask JSON parsing errors at the call site rather than within this function.


453-456: Missing await on response.json() return.

Same pattern issue - returning response.json() without await.

apps/web/utils/email/fastmail.ts (3)

1342-1355: draftEmail uses formatted address string in JMAP to field.

When args.to is not provided, email.headers.from (which may be "Name " format from parseEmailAddress) is used directly in to: [{ email: to }]. JMAP's email address objects expect just the email address in the email field, not the formatted string.


1406-1421: replyToEmail has the same formatted address issue.

email.headers.from returns "Name " format, but it's used directly in to: [{ email: to }]. This could cause delivery issues when the sender has a display name.


2469-2476: Base64 encoding via string concatenation is O(n²) for large attachments.

The loop binary += String.fromCharCode(bytes[i]) creates a new string on each iteration, which is O(n²) for large files. For a 10MB attachment, this could cause significant performance issues.

🧹 Nitpick comments (6)
apps/web/app/api/fastmail/linking/callback/route.ts (2)

186-193: Inconsistent expires_at storage format compared to saveTokens expectations.

The callback stores expires_at as a Date object (new Date(Date.now() + tokens.expires_in * 1000)), but saveTokens in auth.ts expects expires_at as a Unix timestamp in seconds (it multiplies by 1000 when creating the Date). This inconsistency is handled in getFastmailClientWithRefresh via the heuristic check (line 372-375 in client.ts), but it would be cleaner to store consistently as Unix seconds.

🔎 Consider storing as Unix timestamp in seconds for consistency:
             expires_at: tokens.expires_in
-              ? new Date(Date.now() + tokens.expires_in * 1000)
+              ? Math.floor(Date.now() / 1000) + tokens.expires_in
               : null,

255-267: Token update path also has the same expires_at format inconsistency.

Same issue as the create path - storing as Date object instead of Unix timestamp in seconds.

apps/web/utils/fastmail/client.ts (2)

226-235: getLinkingOAuth2Config returns empty strings for missing credentials.

While the callback route now validates these credentials before use, this function still silently returns empty strings. The callback handles this correctly, but other callers might not.

Consider adding validation here to fail fast, or at minimum add a JSDoc warning that callers must validate the returned values.


406-428: Token refresh validation could be more robust.

After refreshing tokens, tokens.access_token is used without validation. If the token endpoint returns an error with HTTP 200 (some OAuth servers do this), newAccessToken could be undefined.

🔎 Add validation for the refreshed access token:
   const tokens = await response.json();
   const newAccessToken = tokens.access_token;
   const newRefreshToken = tokens.refresh_token;

+  if (!newAccessToken) {
+    logger.error("Token refresh response missing access_token", { emailAccountId });
+    throw new SafeError("Token refresh failed: no access token in response");
+  }
+
   // Always save tokens after successful refresh...
apps/web/utils/email/fastmail.ts (2)

1864-1873: removeThreadLabels makes sequential API calls instead of batching.

Each label removal is a separate request. For efficiency, consider batching all label removals into a single request.

🔎 Batch label removals into a single request:
   async removeThreadLabels(
     threadId: string,
     labelIds: string[],
   ): Promise<void> {
     if (labelIds.length === 0) return;

-    for (const labelId of labelIds) {
-      await this.removeThreadLabel(threadId, labelId);
-    }
+    // Get all emails in the thread
+    const threadResponse = await this.client.request([
+      [
+        "Email/query",
+        {
+          accountId: this.client.accountId,
+          filter: { inThread: threadId },
+        },
+        "0",
+      ],
+    ]);
+
+    const emailIds = getResponseData<JMAPQueryResponse>(
+      threadResponse.methodResponses[0],
+    ).ids;
+
+    if (emailIds.length === 0) return;
+
+    // Remove all labels from all emails in one request
+    const update: Record<string, Record<string, boolean>> = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {};
+      for (const labelId of labelIds) {
+        update[emailId][`mailboxIds/${labelId}`] = false;
+      }
+    }
+
+    await this.client.request([
+      [
+        "Email/set",
+        {
+          accountId: this.client.accountId,
+          update,
+        },
+        "0",
+      ],
+    ]);
   }

1064-1126: Redundant mailbox lookup inside loop in bulkArchiveFromSenders.

The inbox lookup is performed inside the for loop for each sender. While the cache makes subsequent calls fast, it's cleaner to move it outside the loop.

🔎 Move inbox lookup outside the loop:
   async bulkArchiveFromSenders(
     fromEmails: string[],
     ownerEmail: string,
     _emailAccountId: string,
   ): Promise<void> {
     const log = this.logger.with({
       action: "bulkArchiveFromSenders",
       sendersCount: fromEmails.length,
     });

+    const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
+    if (!inbox) {
+      log.warn("Inbox mailbox not found");
+      return;
+    }
+    const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);
+
     for (const sender of fromEmails) {
-      const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
-      if (!inbox) continue;
-
       // Query for all emails from this sender in inbox
       // ... rest of the loop
-        const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68a85b2 and b0819eb.

📒 Files selected for processing (3)
  • apps/web/app/api/fastmail/linking/callback/route.ts (1 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
  • apps/web/utils/fastmail/client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Create GET API routes using withAuth or withEmailAccount middleware in apps/web/app/api/*/route.ts, export response types as GetExampleResponse type alias for client-side type safety
Always export response types from GET routes as Get[Feature]Response using type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions with next-safe-action instead

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/**/route.ts

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

**/app/**/route.ts: Always wrap GET API route handlers with withAuth or withEmailAccount middleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes using Awaited<ReturnType<typeof functionName>> pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses using NextResponse.json() in GET API routes
Do not use try/catch blocks in GET API route handlers when using withAuth or withEmailAccount middleware, as the middleware handles error handling

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/**/[!.]*/route.{ts,tsx}

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

Use kebab-case for route directories in Next.js App Router (e.g., api/hello-world/route)

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/api/**/*.{ts,tsx}

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

apps/web/app/api/**/*.{ts,tsx}: API routes must use withAuth, withEmailAccount, or withError middleware for authentication
All database queries must include user scoping with emailAccountId or userId filtering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throw SafeError instead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields using select in database queries to prevent unintended information disclosure
Cron endpoints must use hasCronSecret or hasPostCronSecret to validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/api/**/*.ts

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

**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: use withEmailAccount for email-scoped operations, use withAuth for user-scoped operations, or use withError with proper validation for public/custom auth endpoints
Use withEmailAccount middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using emailAccountId
Use withAuth middleware for user-level operations such as user settings, API keys, and referrals that use only userId
Use withError middleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST use hasCronSecret() or hasPostCronSecret() validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret using hasCronSecret(request) or hasPostCronSecret(request) and capture unauthorized attempts with captureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/app/api/**/route.ts

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

apps/web/**/app/api/**/route.ts: Wrap GET API routes with withAuth or withEmailAccount middleware
Export response types from GET API routes for type-safe client use (e.g., GetExampleResponse)

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/{app/api,utils/actions}/**/*.ts

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

apps/web/**/{app/api,utils/actions}/**/*.ts: Use server actions for all mutations (create/update/delete operations) instead of POST API routes
Use withAuth for user-level operations and withEmailAccount for email-account-level operations

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/{app,pages}/**/{route,+page}.{ts,tsx}

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

**/{app,pages}/**/{route,+page}.{ts,tsx}: Use middleware wrappers (withError, withAuth, withEmailAccount, withEmailProvider) that automatically create loggers with request context in API routes
Enrich logger context within route handlers using logger.with() to add request-specific fields like messageId

Files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: use `withEmailAccount` for email-scoped operations, use `withAuth` for user-scoped operations, or use `withError` with proper validation for public/custom auth endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Wrap GET API routes with `withAuth` or `withEmailAccount` middleware

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: `withEmailAccount` for email-scoped operations, `withAuth` for user-scoped operations, or `withError` with proper validation for public/cron endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Do not use try/catch blocks in GET API route handlers when using `withAuth` or `withEmailAccount` middleware, as the middleware handles error handling

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't hardcode sensitive data like API keys and tokens

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Always validate that resources belong to the authenticated user before any operation - use ownership checks in queries (e.g., `emailAccount: { id: emailAccountId }`) and throw `SafeError` if validation fails

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (2)
apps/web/utils/fastmail/client.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/utils/error.ts (1)
  • SafeError (58-68)
apps/web/env.ts (1)
  • env (17-257)
apps/web/utils/fastmail/scopes.ts (1)
  • SCOPES (8-17)
apps/web/utils/auth.ts (1)
  • saveTokens (456-530)
apps/web/utils/email/fastmail.ts (7)
apps/web/utils/fastmail/client.ts (2)
  • JMAPMethodResponse (159-159)
  • FastmailClient (209-220)
apps/web/utils/logger.ts (2)
  • Logger (5-5)
  • createScopedLogger (17-81)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (22-39)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (12-17)
⏰ 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: Socket Security: Pull Request Alerts
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (14)
apps/web/app/api/fastmail/linking/callback/route.ts (4)

1-22: LGTM - Imports and setup are well-organized.

The imports are appropriately scoped, and the use of withError middleware is correct for OAuth callback routes that need custom authentication handling.


27-36: Environment variable validation is properly implemented.

Good addition of environment variable checks at the start of the handler. This provides clear error messaging when Fastmail OAuth is not configured, matching the pattern used in the Outlook callback.


117-124: Refresh token validation added.

Good catch on requiring the refresh_token. The error message helpfully suggests the offline_access scope may be missing.


285-320: Merge account flow is well-implemented.

The merge flow correctly handles account reassignment, logs appropriate context, and caches the result before redirecting.

apps/web/utils/fastmail/client.ts (2)

44-58: Retry-After header parsing now handles both formats correctly.

Good fix - the code now properly handles both numeric seconds and HTTP-date format per RFC 7231. The fallback to exponential backoff when parsing fails is appropriate.


367-379: Clever handling of inconsistent expires_at storage formats.

The heuristic to detect seconds vs milliseconds based on magnitude (< 32_503_680_000) is pragmatic but adds complexity. This would be unnecessary if storage was consistent.

apps/web/utils/email/fastmail.ts (8)

1-25: Well-organized imports and type definitions.

The imports are appropriate, and the file structure with constants and type definitions at the top aids readability.


26-54: Good extraction of EMAIL_PROPERTIES constant.

This reduces duplication and ensures consistency across Email/get requests.


342-347: parseEmailAddress returns formatted "Name " string.

This is used correctly for display purposes in ParsedMessage.headers, but is problematic when used directly for JMAP email fields (see later comments).


1781-1787: Keywords update now correctly uses JSON Pointer notation.

Good fix - using "keywords/$seen": read patches only the $seen keyword while preserving other keywords like $flagged.


2754-2769: isSentMessage is now correctly synchronous.

Good fix - the method now uses the cached mailbox lookup synchronously instead of incorrectly using async callback in Array.some().


983-1007: Archive operations correctly use JSON Pointer notation.

The archiveThread method properly uses mailboxIds/${inbox.id} patch notation to preserve other mailbox memberships.


1961-1989: Filter operations correctly return unsupported status.

Good implementation - JMAP doesn't have native filter support, and the methods appropriately log warnings and return 501 status codes.


202-219: Provider class structure is well-designed.

The constructor correctly accepts an optional logger and creates a scoped logger with provider context. The toJSON method aids debugging.

@marcodejongh
Copy link
Author

marcodejongh commented Dec 19, 2025

@elie222
Screenshot 2025-12-19 at 11 51 02

Assistant is still broken, but the bulk news letter unsubscribe is able to load data. Got the assistants screen working too, but theres some pagination issues with the Fastmail integration. I also added Authelia OIDC auth so I didnt have to use gmail or outlook oAuth.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/app/(landing)/welcome-redirect/page.tsx (1)

21-29: LGTM! Consider optional performance optimization.

The logic correctly handles users from auth-only providers (like Authelia) who don't have email accounts yet. The redirect flow is sound and the security filtering is proper.

Optional: Combine DB queries for better performance

You can reduce database round-trips from 2 to 1 by including the email account check in the initial user query:

 const user = await prisma.user.findUnique({
   where: { id: session.user.id },
-  select: { completedOnboardingAt: true, utms: true },
+  select: { 
+    completedOnboardingAt: true, 
+    utms: true,
+    emailAccounts: { take: 1, select: { id: true } }
+  },
 });

 // Session exists but user doesn't - invalid state, log out
 if (!user) redirect("/logout");

-// Check if user has any email accounts
-// Users who logged in with auth-only providers (like Authelia) won't have one yet
-const emailAccount = await prisma.emailAccount.findFirst({
-  where: { userId: session.user.id },
-  select: { id: true },
-});
-
 // No email account yet - redirect to accounts page to add one
-if (!emailAccount) redirect("/accounts");
+if (user.emailAccounts.length === 0) redirect("/accounts");
apps/web/app/(landing)/login/LoginForm.tsx (1)

144-156: Use the validated env import instead of direct process.env access.

The codebase uses @t3-oss/env-nextjs for environment variable validation. Accessing process.env.NEXT_PUBLIC_AUTHELIA_ENABLED directly bypasses the Zod validation and type coercion defined in env.ts.

🔎 Proposed fix
+import { env } from "@/env";
 // ... existing imports
 
-      {process.env.NEXT_PUBLIC_AUTHELIA_ENABLED === "true" && (
+      {env.NEXT_PUBLIC_AUTHELIA_ENABLED && (

This ensures you get the properly coerced boolean value from the validated environment configuration.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0819eb and 0dca77e.

📒 Files selected for processing (6)
  • apps/web/.env.example (1 hunks)
  • apps/web/app/(landing)/login/LoginForm.tsx (3 hunks)
  • apps/web/app/(landing)/welcome-redirect/page.tsx (1 hunks)
  • apps/web/env.ts (3 hunks)
  • apps/web/utils/auth-client.ts (1 hunks)
  • apps/web/utils/auth.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.tsx

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/app/(landing)/login/LoginForm.tsx
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/.env.example
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/auth-client.ts
  • apps/web/utils/auth.ts
apps/web/**/{.env.example,env.ts,turbo.json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/.env.example
  • apps/web/env.ts
apps/web/env.ts

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

apps/web/env.ts: Add server-only environment variables to apps/web/env.ts under the server object with Zod schema validation
Add client-side environment variables to apps/web/env.ts under the client object with NEXT_PUBLIC_ prefix and Zod schema validation
Add client-side environment variables to apps/web/env.ts under the experimental__runtimeEnv object to enable runtime access

Files:

  • apps/web/env.ts
{.env.example,apps/web/env.ts}

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

Client-side environment variables must be prefixed with NEXT_PUBLIC_

Files:

  • apps/web/env.ts
**/*Form.{ts,tsx}

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

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

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/**/*{Form,Form.tsx}

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

Use React Hook Form with Zod validation for form handling

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{app/api,utils/actions}/**/*.ts : Use `withAuth` for user-level operations and `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Use `withAuth` middleware to get the authenticated user or `withEmailAccount` middleware to get the currently active email account in GET API routes

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All database queries must include user/account filtering with `emailAccountId` or `userId` in WHERE clauses to prevent IDOR vulnerabilities

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: `withEmailAccount` for email-scoped operations, `withAuth` for user-scoped operations, or `withError` with proper validation for public/cron endpoints

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : All database queries must include user scoping with `emailAccountId` or `userId` filtering in WHERE clauses

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router

Applied to files:

  • apps/web/app/(landing)/welcome-redirect/page.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`

Applied to files:

  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/auth.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to .env.example : Add new environment variables to `.env.example` with example values

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{.env.example,env.ts,turbo.json} : Add environment variables to `.env.example`, `env.ts`, and `turbo.json`

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-07-19T15:06:43.730Z
Learnt from: garnertb
Repo: elie222/inbox-zero PR: 580
File: apps/web/.env.example:4-7
Timestamp: 2025-07-19T15:06:43.730Z
Learning: In apps/web/.env.example, boolean environment variables follow an unquoted convention (e.g., LOG_ZOD_ERRORS=true, AUTH_TRUST_HOST=true). Logical grouping of related variables (like auth variables together) is preferred over strict alphabetical ordering for better developer experience.

Applied to files:

  • apps/web/.env.example
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `experimental__runtimeEnv` object to enable runtime access

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to .env.example : Add environment variables to `.env.example` with example values in the format `VARIABLE_NAME=value_example`

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Prefix client-side environment variables with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add server-only environment variables to `apps/web/env.ts` under the `server` object with Zod schema validation

Applied to files:

  • apps/web/.env.example
  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to turbo.json : Add new environment variables to `turbo.json` under `tasks.build.env` as a global dependency for the build task

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to turbo.json : Add environment variables to `turbo.json` under `tasks.build.env` array to declare build-time dependencies

Applied to files:

  • apps/web/env.ts
🧬 Code graph analysis (3)
apps/web/utils/auth-client.ts (1)
apps/web/env.ts (1)
  • env (17-262)
apps/web/utils/auth.ts (2)
apps/web/env.ts (1)
  • env (17-262)
apps/web/utils/email/provider-types.ts (1)
  • isFastmailProvider (9-11)
apps/web/app/(landing)/login/LoginForm.tsx (1)
apps/web/utils/config.ts (1)
  • WELCOME_PATH (24-24)
🔇 Additional comments (9)
apps/web/utils/auth-client.ts (1)

4-12: LGTM!

The genericOAuthClient plugin is correctly imported and added to the client-side auth configuration, enabling support for generic OAuth providers like Authelia. This properly complements the server-side genericOAuth plugin configuration in auth.ts.

apps/web/utils/auth.ts (4)

69-87: LGTM! Authelia OIDC provider configuration looks correct.

The conditional spread pattern cleanly enables Authelia only when all required environment variables are present. The scopes (openid, profile, email) are appropriate for OIDC authentication.


374-397: Authelia profile fetching implementation is correct.

The error handling is appropriate, and the fallback from name to preferred_username is a good practice for OIDC providers. The lowercasing of email is consistent with other providers.


399-411: LGTM!

The Fastmail profile fetching correctly delegates to the dedicated utility and maintains consistency with other provider implementations. The comment explaining the undefined image is helpful.


414-423: LGTM!

Correctly skipping EmailAccount creation for auth-only providers like Authelia. The logging provides good observability for debugging account linking flows.

apps/web/env.ts (2)

29-33: LGTM!

Server-side environment variables for Fastmail and Authelia are correctly defined with optional string schemas. The inline comment for AUTHELIA_ISSUER_URL provides helpful documentation.


198-198: LGTM!

The client-side NEXT_PUBLIC_AUTHELIA_ENABLED variable is correctly:

  1. Prefixed with NEXT_PUBLIC_ as required
  2. Defined with z.coerce.boolean().optional() for proper type coercion
  3. Added to experimental__runtimeEnv for runtime access

Also applies to: 260-260

apps/web/app/(landing)/login/LoginForm.tsx (2)

20-20: LGTM!

The ShieldCheckIcon import and loadingAuthelia state follow the established patterns for other sign-in providers.

Also applies to: 28-28


68-85: LGTM!

The handleAutheliaSignIn function correctly uses signIn.oauth2 with providerId: "authelia" for the generic OAuth flow. Error handling is consistent with the Google and Microsoft handlers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/utils/email/fastmail.ts (2)

1113-1115: Consider moving mailbox lookup outside the loop for clarity.

The inbox mailbox lookup is repeated inside the sender loop. While this is efficient due to caching (the cache makes subsequent calls O(1)), moving it outside the loop would improve code readability and make the caching optimization more explicit.

🔎 Suggested refactor
+    const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
+    if (!inbox) {
+      log.warn("Inbox mailbox not found");
+      return;
+    }
+
     for (const sender of fromEmails) {
-      const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
-      if (!inbox) continue;
-
       // Query for all emails from this sender in inbox

1903-1912: Consider batching label removal operations.

The method makes sequential API calls for each label removal, requiring N round trips for N labels. Since JMAP's Email/set supports batching multiple updates in a single request, you could optimize this by removing all labels in one call.

🔎 Suggested optimization
   async removeThreadLabels(
     threadId: string,
     labelIds: string[],
   ): Promise<void> {
     if (labelIds.length === 0) return;

-    for (const labelId of labelIds) {
-      await this.removeThreadLabel(threadId, labelId);
-    }
+    // Get all emails in the thread
+    const threadResponse = await this.client.request([
+      [
+        "Email/query",
+        {
+          accountId: this.client.accountId,
+          filter: { inThread: threadId },
+        },
+        "0",
+      ],
+    ]);
+
+    const emailIds = getResponseData<JMAPQueryResponse>(
+      threadResponse.methodResponses[0],
+    ).ids;
+
+    if (emailIds.length === 0) return;
+
+    // Remove all labels from all emails in one batch
+    const update: Record<string, Record<string, boolean>> = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {};
+      for (const labelId of labelIds) {
+        update[emailId][`mailboxIds/${labelId}`] = false;
+      }
+    }
+
+    await this.client.request([
+      [
+        "Email/set",
+        {
+          accountId: this.client.accountId,
+          update,
+        },
+        "0",
+      ],
+    ]);
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dca77e and ff84b20.

📒 Files selected for processing (2)
  • apps/web/providers/EmailProvider.tsx (2 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.tsx

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/providers/EmailProvider.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/providers/EmailProvider.tsx
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/providers/EmailProvider.tsx
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/providers/EmailProvider.tsx
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/email/fastmail.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/email/fastmail.ts
🧠 Learnings (9)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/providers/EmailProvider.tsx
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (2)
apps/web/providers/EmailProvider.tsx (1)
apps/web/utils/email/provider-types.ts (1)
  • isFastmailProvider (9-11)
apps/web/utils/email/fastmail.ts (8)
apps/web/utils/fastmail/client.ts (3)
  • JMAPMethodResponse (159-159)
  • FastmailClient (209-220)
  • getAccessTokenFromClient (463-465)
apps/web/utils/email/types.ts (1)
  • EmailProvider (45-245)
apps/web/utils/logger.ts (2)
  • Logger (5-5)
  • createScopedLogger (17-81)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (22-39)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (12-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (1)
apps/web/providers/EmailProvider.tsx (1)

10-10: LGTM! Fastmail label color handling is correct.

The implementation correctly identifies Fastmail providers and returns undefined for label colors, which aligns with JMAP's mailbox model that doesn't support color attributes. The explanatory comment is helpful.

Also applies to: 56-59

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

2560-2573: Optimize base64 encoding to avoid O(n²) string concatenation.

Lines 2563-2566 use string concatenation in a loop to build the binary string, which is O(n²) due to string immutability in JavaScript. For large attachments (e.g., 10MB+), this will cause severe performance degradation and potential timeouts.

🔎 Proposed fix using Buffer (Node.js)
 const buffer = await response.arrayBuffer();
-// Convert ArrayBuffer to base64 using Web APIs
-const bytes = new Uint8Array(buffer);
-let binary = "";
-for (let i = 0; i < bytes.byteLength; i++) {
-  binary += String.fromCharCode(bytes[i]);
-}
-const base64 = btoa(binary);
+// Convert ArrayBuffer to base64 efficiently
+const base64 = Buffer.from(buffer).toString('base64');
 
 return {
   data: base64,
   size: buffer.byteLength,
 };

1462-1530: Extract email address from formatted string in reply recipient.

Line 1488 assigns email.headers.from directly to to, which contains the full "Name " format. Line 1499 then uses this in the JMAP email field, which expects only the email address. This will cause the reply to fail when the original sender has a display name.

🔎 Proposed fix
+import { isValidEmail, extractEmailAddress } from "@/utils/email";
 
 // ... in replyToEmail method:
 
 const to = email.headers.from;
+const toEmail = extractEmailAddress(to);
 
 await this.client.request([
   [
     "Email/set",
     {
       accountId: this.client.accountId,
       create: {
         reply: {
           mailboxIds: { [sent.id]: true },
           from: [{ email: identity.email, name: identity.name }],
-          to: [{ email: to }],
+          to: [{ email: toEmail }],
           subject: `Re: ${email.subject}`,
🧹 Nitpick comments (2)
apps/web/utils/email/fastmail.ts (2)

1-7: Remove unused type imports.

JMAPMethodCall and JMAPError are imported but never used in this file.

🔎 Suggested fix
 import type {
   FastmailClient,
-  JMAPMethodCall,
   JMAPMethodResponse,
-  JMAPError,
 } from "@/utils/fastmail/client";

1943-1952: Consider batching label removals for better performance.

This method makes sequential API calls in a loop. For multiple labels, consider batching all updates into a single Email/set request by building the update object with all label removals at once (similar to the pattern used in archiveThread and bulkArchiveFromSenders).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff84b20 and b0c377c.

📒 Files selected for processing (2)
  • apps/web/utils/email.ts (1 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
🧠 Learnings (9)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{app/api,utils/actions}/**/*.ts : Use `withAuth` for user-level operations and `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/utils/email.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`

Applied to files:

  • apps/web/utils/email.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/email/fastmail.ts
🔇 Additional comments (2)
apps/web/utils/email.ts (1)

54-56: LGTM!

The export addition enables the Fastmail provider to validate email addresses before bulk operations, which is a good safety practice.

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

1116-1121: Excellent safety validation for bulk operations.

The email validation check before processing bulk operations is a good defensive practice that prevents accidental operations with malformed data.

@marcodejongh
Copy link
Author

@elie222 Seems to be working now, had it running for most of the day just havent customised the rules yet.

claude and others added 6 commits December 19, 2025 14:36
This implements Fastmail as a third email provider alongside Gmail and
Microsoft/Outlook. Uses the JMAP (JSON Meta Application Protocol) which
is Fastmail's native API.

Key changes:
- Add FastmailProvider class implementing the EmailProvider interface
- Add JMAP client utilities for session management and API calls
- Add OAuth flow routes for Fastmail account linking
- Update provider factory to support Fastmail
- Add Fastmail to UI account linking options
- Add environment variables for FASTMAIL_CLIENT_ID and FASTMAIL_CLIENT_SECRET

Features supported:
- Email read/send/reply/forward
- Thread management (archive, trash, mark read)
- Mailbox/folder management (JMAP uses mailboxes instead of Gmail labels)
- Bulk operations
- Attachments

Note: Some features like Gmail-style filters and Pub/Sub watching use
different mechanisms in JMAP and have limited/no support initially.
- Fix JMAP mailboxIds patching to use JSON Pointer notation instead of
  replacing the entire object, preserving other mailbox memberships
- Add FASTMAIL_CLIENT_ID and FASTMAIL_CLIENT_SECRET to .env.example
- Add Fastmail env vars to turbo.json build.env
- Add comprehensive JSDoc documentation to Fastmail modules
- Add uploadBlob method to upload files to Fastmail using JMAP blob upload
- Update sendEmailWithHtml to upload and include attachments in emails
- Previously the attachments parameter was declared but never used
The core JMAP scope (urn:ietf:params:jmap:core) is required for all
JMAP operations and should be requested before other JMAP scopes.
- Fix token expiry check: expiresAt is stored in seconds but was compared
  against Date.now() milliseconds - now multiplies by 1000 for correct comparison
- Fix isSentMessage: async callback in some() doesn't work - now uses
  synchronous cache lookup with proper loop
- Add env var validation in callback route: check FASTMAIL_CLIENT_ID and
  FASTMAIL_CLIENT_SECRET before proceeding
- Replace Google-specific access_type: "offline" with OIDC standard
  offline_access scope for getting refresh tokens
Critical fixes:
- checkIfReplySent now returns false on error (was silently skipping replies)
- Fixed token expiration handling for both seconds and milliseconds formats
- Always save tokens after refresh to handle rotated refresh tokens
- Added configuration validation in auth-url route

High priority fixes:
- labelMessage only retries on specific JMAP errors (invalidArguments/notFound)
- Added JMAP method-level error checking (checkJMAPErrors)
- Added email domain validation to prevent invalid JMAP filters
- Added try-catch to ensureMailboxCache, getThreads, getThread methods
- Added try-catch with logging for getUserInfo in callback

Medium priority fixes:
- Added exponential backoff retry logic for transient errors (429/5xx)
- Added session capabilities validation for required JMAP capabilities
- Parallelized attachment uploads using Promise.all
- Fixed keyword handling logic for UNREAD label

Low priority fixes:
- Extracted EMAIL_PROPERTIES constant to reduce duplication
- Added captureException for token exchange failures
- Added fallback values for optional scope and token_type fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/app/(app)/accounts/AddAccount.tsx (1)

89-110: Fastmail CTA + app-token modal wiring is correct; consider gating modal behind the flag

The Fastmail button and “Use App Token instead” CTA are properly gated behind fastmailEnabled and respect isAnyLoading, and FastmailAppTokenModal is wired with simple open/onOpenChange state. Functionally this looks good.

If you want to avoid mounting the modal when Fastmail is entirely disabled (minor clarity/optimization), you could also guard the modal with the same flag:

Proposed optional refactor
-      <FastmailAppTokenModal
-        open={showAppTokenModal}
-        onOpenChange={setShowAppTokenModal}
-      />
+      {fastmailEnabled && (
+        <FastmailAppTokenModal
+          open={showAppTokenModal}
+          onOpenChange={setShowAppTokenModal}
+        />
+      )}

As per coding guidelines, this keeps the Fastmail-specific UI fully behind its feature flag without changing behavior.

Also applies to: 117-120

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0c377c and d09eff6.

📒 Files selected for processing (4)
  • apps/web/.env.example (1 hunks)
  • apps/web/app/(app)/accounts/AddAccount.tsx (5 hunks)
  • apps/web/env.ts (3 hunks)
  • apps/web/hooks/useFeatureFlags.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/.env.example
  • apps/web/env.ts
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/hooks/use*.ts

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

apps/web/hooks/use*.ts: Use SWR hooks for client-side data fetching, with hooks stored in apps/web/hooks/use*.ts that return typed responses from GET API routes
Call mutate() on SWR hooks after successful mutations to refresh cached data

apps/web/hooks/use*.ts: Use the use prefix for custom hook filenames (e.g., useAccounts.ts)
For data fetching in custom hooks, prefer using useSWR and wrap it to handle API endpoint URL, returning data, loading state, error state, and potentially the mutate function
Create dedicated hooks for specific data types (e.g., useAccounts, useLabels) to wrap useSWR for individual API endpoints

Files:

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

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

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

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/hooks/useFeatureFlags.ts

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

Feature flag hooks should be defined in apps/web/hooks/useFeatureFlags.ts with two patterns: boolean flags using useFeatureFlagEnabled("key") and variant flags using useFeatureFlagVariantKey("key") with type casting

Files:

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

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

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

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

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

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

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

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

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

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/hooks/*.ts

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

Use SWR for client-side data fetching with type-safe response types

Files:

  • apps/web/hooks/useFeatureFlags.ts
apps/web/app/(app)/**/*.{ts,tsx}

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

apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

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

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

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

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
🧠 Learnings (15)
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : For early access features, create boolean flag hooks using `useFeatureFlagEnabled` with the pattern `export function use[FeatureName]Enabled()`

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Feature flag hooks should be defined in `apps/web/hooks/useFeatureFlags.ts` with two patterns: boolean flags using `useFeatureFlagEnabled("key")` and variant flags using `useFeatureFlagVariantKey("key")` with type casting

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : All feature flag hooks should be defined in `apps/web/hooks/useFeatureFlags.ts`

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : For early access feature flags, create hooks using the naming convention `use[FeatureName]Enabled` that return a boolean from `useFeatureFlagEnabled("flag-key")`

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Use `use[FeatureName]Enabled` naming convention for boolean feature flag hooks and `use[FeatureName]Variant` for A/B test variant hooks

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Centralize all feature flag hooks in `apps/web/hooks/useFeatureFlags.ts` rather than scattering them across multiple files

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : For A/B test variant flags, create hooks using the naming convention `use[FeatureName]Variant` that define variant types, use `useFeatureFlagVariantKey()` with type casting, and provide a default "control" fallback

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : For A/B test variants, create hooks using `useFeatureFlagVariantKey` with the pattern `export function use[FeatureName]Variant()` and always provide a default fallback value

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Always define explicit types for variant flags and provide a default/control fallback value

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Use kebab-case for feature flag keys (e.g., `inbox-cleaner`, `pricing-options-2`)

Applied to files:

  • apps/web/hooks/useFeatureFlags.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
🧬 Code graph analysis (2)
apps/web/hooks/useFeatureFlags.ts (1)
apps/web/env.ts (1)
  • env (17-264)
apps/web/app/(app)/accounts/AddAccount.tsx (2)
apps/web/hooks/useFeatureFlags.ts (1)
  • useFastmailEnabled (19-21)
apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (1)
  • FastmailAppTokenModal (27-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (2)
apps/web/hooks/useFeatureFlags.ts (1)

19-21: Fastmail feature-flag hook matches existing env-backed pattern

useFastmailEnabled cleanly wraps env.NEXT_PUBLIC_FASTMAIL_ENABLED, consistent with useMeetingBriefsEnabled / useIntegrationsEnabled and the centralization of feature-flag hooks in this file. No changes suggested.

Based on learnings, this is the intended place and pattern for new boolean feature flags.

apps/web/app/(app)/accounts/AddAccount.tsx (1)

10-20: Provider union + shared loading state refactor looks solid

Typing Provider as a union and driving both the loading setters and error titles off Provider/PROVIDER_DISPLAY_NAMES makes the flow extensible and keeps handleAddAccount DRY. The aggregated isAnyLoading flag correctly prevents concurrent link starts across Google/Microsoft/Fastmail and is wired into all primary CTAs. No behavioral issues spotted.

As per coding guidelines, this centralization of provider-specific behavior improves maintainability.

Also applies to: 25-38, 47-47, 63-63, 79-79

marcodejongh and others added 5 commits December 19, 2025 14:36
For self-hosted users who don't have OAuth client credentials,
this adds the ability to connect Fastmail using an app token
generated in Fastmail settings.

- Add "Use App Token instead" link below Fastmail OAuth button
- Create modal dialog for app token entry
- Validate token by connecting to JMAP before saving
- Store app token accounts with type="app_token" and no refresh token
- Modify Fastmail client to skip token refresh for app token accounts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix Retry-After header parsing: handle HTTP-date strings per RFC 7231,
  previously would cause immediate retry (0ms) when header was a date
- Add refresh_token validation in OAuth callback: fail early if no
  refresh token to prevent creating broken accounts that can't refresh
- Fix markReadThread keywords: use JMAP path notation "keywords/$seen"
  instead of replacing entire keywords object, preserving other flags
  like $flagged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two issues fixed:

1. Pagination only loading 20 emails:
   - Added `calculateTotal: true` to Email/query requests (required by JMAP spec)
   - Created `calculateNextPageToken()` helper with fallback for robustness
   - Applied to getMessagesWithPagination, getMessagesFromSender, getThreadsWithQuery

2. Sent emails not appearing in analytics:
   - Added SENT/INBOX synthetic labels in parseJMAPEmail based on mailbox roles
   - Ensures compatibility with Gmail-style label checks (labelIds.includes("SENT"))
   - Added ensureMailboxCache() calls before parsing to guarantee cache availability

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
marcodejongh and others added 2 commits December 19, 2025 14:36
…nbox

JMAP requires `null` to remove emails from mailboxes, not `false`.
The previous code was only adding emails to archive/trash without
actually removing them from the inbox.

Changes:
- Export isValidEmail for use in bulk operation validation
- Add email validation guard to prevent accidental bulk operations
- Fix all mailbox removal operations to use `null` instead of `false`
- Add logging for bulk operation query results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Hide Fastmail provider behind NEXT_PUBLIC_FASTMAIL_ENABLED env var
(defaults to false) to allow controlled rollout. This follows the
existing pattern used for other optional features like integrations
and meeting briefs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@marcodejongh marcodejongh force-pushed the claude/add-fastmail-support-bu4nM branch from d09eff6 to bd68c01 Compare December 19, 2025 03:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
apps/web/utils/email/fastmail.ts (3)

1420-1433: Extract email address from formatted string for JMAP email field.

The to variable may contain the full "Name " format from email.headers.from (via parseEmailAddress), but JMAP's email field expects only the email address. This could cause draft creation to fail when args.to is not provided and the original sender has a display name.

🔎 Proposed fix
+import { extractEmailAddress } from "@/utils/email";
 
 // ... in draftEmail method:
 
-const to = args.to || email.headers.from;
+const toRaw = args.to || email.headers.from;
+const to = extractEmailAddress(toRaw) || toRaw;
 const subject = args.subject || `Re: ${email.subject}`;

1488-1499: Same issue: Extract email address from formatted string.

Similar to draftEmail, the to variable contains the formatted "Name " string from email.headers.from, but JMAP expects only the email address in the email field.

🔎 Proposed fix
+import { extractEmailAddress } from "@/utils/email";
 
 // ... in replyToEmail method:
 
-const to = email.headers.from;
+const to = extractEmailAddress(email.headers.from) || email.headers.from;

2560-2572: String concatenation in loop is O(n²) for large attachments.

The base64 conversion uses string concatenation in a loop, which has O(n²) time complexity and will cause severe performance issues for large attachments.

🔎 Use Buffer for efficient base64 encoding
     const buffer = await response.arrayBuffer();
-    // Convert ArrayBuffer to base64 using Web APIs
-    const bytes = new Uint8Array(buffer);
-    let binary = "";
-    for (let i = 0; i < bytes.byteLength; i++) {
-      binary += String.fromCharCode(bytes[i]);
-    }
-    const base64 = btoa(binary);
+    // Use Buffer for efficient base64 conversion (Node.js runtime)
+    const base64 = Buffer.from(buffer).toString("base64");
 
     return {
       data: base64,
       size: buffer.byteLength,
     };
🧹 Nitpick comments (4)
apps/web/utils/email/fastmail.ts (2)

1116-1124: Move inbox lookup outside the loop for consistency.

The inbox lookup is performed inside the for loop for each sender. While ensureMailboxCache returns cached data after the first call, this is still redundant and inconsistent with bulkTrashFromSenders (lines 1195-1199) which correctly fetches inbox and trash outside the loop.

🔎 Proposed fix
   async bulkArchiveFromSenders(
     fromEmails: string[],
     ownerEmail: string,
     _emailAccountId: string,
   ): Promise<void> {
     const log = this.logger.with({
       action: "bulkArchiveFromSenders",
       sendersCount: fromEmails.length,
     });
 
+    const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
+    if (!inbox) {
+      log.warn("Inbox mailbox not found");
+      return;
+    }
+    const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);
+
     for (const sender of fromEmails) {
       // Safety check: only process valid email addresses to prevent accidental bulk operations
       if (!isValidEmail(sender)) {
         log.warn("Skipping invalid sender email for bulk archive", { sender });
         continue;
       }
 
-      const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
-      if (!inbox) continue;
-
       // Query for all emails from this sender in inbox
       // ...
       
       if (emailIds.length > 0) {
         // Archive all emails using JSON Pointer notation to patch mailbox flags
-        const archive = await this.getMailboxByRole(FastmailMailbox.ARCHIVE);
         // ...
       }
     }

1943-1952: Consider batching label removals for efficiency.

The method makes sequential API calls for each label. For better performance with multiple labels, consider batching all removals into a single Email/set request.

🔎 Batched implementation
   async removeThreadLabels(
     threadId: string,
     labelIds: string[],
   ): Promise<void> {
     if (labelIds.length === 0) return;
 
-    for (const labelId of labelIds) {
-      await this.removeThreadLabel(threadId, labelId);
-    }
+    // Get all emails in the thread once
+    const threadResponse = await this.client.request([
+      [
+        "Email/query",
+        {
+          accountId: this.client.accountId,
+          filter: { inThread: threadId },
+        },
+        "0",
+      ],
+    ]);
+
+    const emailIds = getResponseData<JMAPQueryResponse>(
+      threadResponse.methodResponses[0],
+    ).ids;
+
+    if (emailIds.length === 0) return;
+
+    // Build update to remove all labels from all emails in one request
+    const update: Record<string, Record<string, boolean | null>> = {};
+    for (const emailId of emailIds) {
+      update[emailId] = {};
+      for (const labelId of labelIds) {
+        update[emailId][`mailboxIds/${labelId}`] = null;
+      }
+    }
+
+    await this.client.request([
+      [
+        "Email/set",
+        {
+          accountId: this.client.accountId,
+          update,
+        },
+        "0",
+      ],
+    ]);
   }
apps/web/utils/fastmail/client.ts (2)

226-235: Add validation for required OAuth credentials.

While the callback route now validates environment variables upfront, getLinkingOAuth2Config should also fail fast when credentials are missing. This function could be called from other contexts where the caller may not validate, leading to obscure OAuth failures with empty credentials.

🔎 Proposed fix
 export function getLinkingOAuth2Config() {
+  if (!env.FASTMAIL_CLIENT_ID || !env.FASTMAIL_CLIENT_SECRET) {
+    throw new SafeError("Fastmail OAuth credentials not configured");
+  }
+
   return {
-    clientId: env.FASTMAIL_CLIENT_ID || "",
-    clientSecret: env.FASTMAIL_CLIENT_SECRET || "",
+    clientId: env.FASTMAIL_CLIENT_ID,
+    clientSecret: env.FASTMAIL_CLIENT_SECRET,
     redirectUri: `${env.NEXT_PUBLIC_BASE_URL}/api/fastmail/linking/callback`,
     authorizeUrl: FASTMAIL_OAUTH_AUTHORIZE_URL,
     tokenUrl: FASTMAIL_OAUTH_TOKEN_URL,
     scopes: SCOPES,
   };
 }

383-394: Consider using fetchWithRetry for token refresh and getUserInfo.

The token refresh (line 383) and getUserInfo (line 440) calls use plain fetch instead of fetchWithRetry, meaning they don't benefit from retry logic for transient errors (429, 503, etc.). While this might be intentional to avoid delays in authentication flows, applying the same retry pattern used elsewhere would improve resilience.

Also applies to: 440-444

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d09eff6 and bd68c01.

📒 Files selected for processing (26)
  • apps/web/.env.example (1 hunks)
  • apps/web/app/(app)/accounts/AddAccount.tsx (5 hunks)
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (1 hunks)
  • apps/web/app/(landing)/login/LoginForm.tsx (3 hunks)
  • apps/web/app/(landing)/welcome-redirect/page.tsx (1 hunks)
  • apps/web/app/api/fastmail/linking/auth-url/route.ts (1 hunks)
  • apps/web/app/api/fastmail/linking/callback/route.ts (1 hunks)
  • apps/web/env.ts (3 hunks)
  • apps/web/hooks/useFeatureFlags.ts (1 hunks)
  • apps/web/providers/EmailProvider.tsx (2 hunks)
  • apps/web/utils/account-linking.ts (2 hunks)
  • apps/web/utils/account.ts (2 hunks)
  • apps/web/utils/actions/fastmail-app-token.ts (1 hunks)
  • apps/web/utils/actions/fastmail-app-token.validation.ts (1 hunks)
  • apps/web/utils/auth-client.ts (1 hunks)
  • apps/web/utils/auth.ts (4 hunks)
  • apps/web/utils/email.ts (1 hunks)
  • apps/web/utils/email/fastmail.ts (1 hunks)
  • apps/web/utils/email/provider-types.ts (1 hunks)
  • apps/web/utils/email/provider.ts (2 hunks)
  • apps/web/utils/email/types.ts (1 hunks)
  • apps/web/utils/fastmail/client.ts (1 hunks)
  • apps/web/utils/fastmail/constants.ts (1 hunks)
  • apps/web/utils/fastmail/scopes.ts (1 hunks)
  • apps/web/utils/oauth/account-linking.ts (1 hunks)
  • turbo.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/web/utils/auth-client.ts
  • apps/web/app/(landing)/welcome-redirect/page.tsx
  • apps/web/utils/email/types.ts
  • turbo.json
  • apps/web/.env.example
  • apps/web/utils/account.ts
  • apps/web/hooks/useFeatureFlags.ts
  • apps/web/utils/auth.ts
  • apps/web/env.ts
  • apps/web/utils/actions/fastmail-app-token.validation.ts
  • apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx
  • apps/web/utils/fastmail/scopes.ts
  • apps/web/providers/EmailProvider.tsx
🧰 Additional context used
📓 Path-based instructions (31)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/utils/email.ts
  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/email.ts
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Create GET API routes using withAuth or withEmailAccount middleware in apps/web/app/api/*/route.ts, export response types as GetExampleResponse type alias for client-side type safety
Always export response types from GET routes as Get[Feature]Response using type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions with next-safe-action instead

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/**/route.ts

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

**/app/**/route.ts: Always wrap GET API route handlers with withAuth or withEmailAccount middleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes using Awaited<ReturnType<typeof functionName>> pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses using NextResponse.json() in GET API routes
Do not use try/catch blocks in GET API route handlers when using withAuth or withEmailAccount middleware, as the middleware handles error handling

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/app/**/[!.]*/route.{ts,tsx}

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

Use kebab-case for route directories in Next.js App Router (e.g., api/hello-world/route)

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/app/api/**/*.{ts,tsx}

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

apps/web/app/api/**/*.{ts,tsx}: API routes must use withAuth, withEmailAccount, or withError middleware for authentication
All database queries must include user scoping with emailAccountId or userId filtering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throw SafeError instead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields using select in database queries to prevent unintended information disclosure
Cron endpoints must use hasCronSecret or hasPostCronSecret to validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/app/api/**/*.ts

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

**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: use withEmailAccount for email-scoped operations, use withAuth for user-scoped operations, or use withError with proper validation for public/custom auth endpoints
Use withEmailAccount middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using emailAccountId
Use withAuth middleware for user-level operations such as user settings, API keys, and referrals that use only userId
Use withError middleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST use hasCronSecret() or hasPostCronSecret() validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret using hasCronSecret(request) or hasPostCronSecret(request) and capture unauthorized attempts with captureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/app/**

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

Follow NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/app/api/**/route.ts

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

apps/web/**/app/api/**/route.ts: Wrap GET API routes with withAuth or withEmailAccount middleware
Export response types from GET API routes for type-safe client use (e.g., GetExampleResponse)

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
apps/web/**/{app/api,utils/actions}/**/*.ts

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

apps/web/**/{app/api,utils/actions}/**/*.ts: Use server actions for all mutations (create/update/delete operations) instead of POST API routes
Use withAuth for user-level operations and withEmailAccount for email-account-level operations

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/{app,pages}/**/{route,+page}.{ts,tsx}

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

**/{app,pages}/**/{route,+page}.{ts,tsx}: Use middleware wrappers (withError, withAuth, withEmailAccount, withEmailProvider) that automatically create loggers with request context in API routes
Enrich logger context within route handlers using logger.with() to add request-specific fields like messageId

Files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
**/*Form.{ts,tsx}

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

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

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
**/*.tsx

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

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/app/(app)/accounts/AddAccount.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use both children and dangerouslySetInnerHTML props on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use <>...</> instead of <Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx}

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

apps/web/**/*.{tsx,jsx}: Prefer functional components with hooks over class components
Use shadcn/ui components when available
Follow consistent naming conventions with PascalCase for component names
Use LoadingContent component for async data with loading and error states
Use result?.serverError with toastError and toastSuccess for mutation error handling

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*.{tsx,jsx,css}

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

Ensure responsive design with mobile-first approach

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
  • apps/web/app/(app)/accounts/AddAccount.tsx
apps/web/**/*{Form,Form.tsx}

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

Use React Hook Form with Zod validation for form handling

Files:

  • apps/web/app/(landing)/login/LoginForm.tsx
apps/web/utils/actions/*.ts

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

apps/web/utils/actions/*.ts: Use next-safe-action with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in apps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from the actionClient

apps/web/utils/actions/*.ts: Create corresponding server action implementation files using the naming convention apps/web/utils/actions/NAME.ts with 'use server' directive
Use 'use server' directive at the top of server action implementation files
Implement all server actions using the next-safe-action library with actionClient, actionClientUser, or adminActionClient for type safety and validation
Use actionClientUser when only authenticated user context (userId) is needed
Use actionClient when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
Use adminActionClient for actions restricted to admin users
Add metadata with a meaningful action name using .metadata({ name: "actionName" }) for Sentry instrumentation and monitoring
Use .schema() method with Zod validation schemas from corresponding .validation.ts files in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via the ctx object parameter in the .action() handler
Use revalidatePath or revalidateTag from 'next/cache' within server action handlers when mutations modify data displayed elsewhere

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
apps/web/utils/actions/**/*.ts

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

apps/web/utils/actions/**/*.ts: Server actions must be located in apps/web/utils/actions folder
Server action files must start with use server directive

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
apps/web/**/utils/actions/*.ts

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

apps/web/**/utils/actions/*.ts: Use next-safe-action with proper Zod validation for server actions
Call revalidatePath in server actions for cache invalidation after mutations

Files:

  • apps/web/utils/actions/fastmail-app-token.ts
apps/web/app/(app)/**/*.{ts,tsx}

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

apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/accounts/AddAccount.tsx
🧠 Learnings (49)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/{app/api,utils/actions}/**/*.ts : Use `withAuth` for user-level operations and `withEmailAccount` for email-account-level operations

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`

Applied to files:

  • apps/web/utils/email.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/email.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Wrap GET API routes with `withAuth` or `withEmailAccount` middleware

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Infer and export the response type for GET API routes using `export type GetResponse = Awaited<ReturnType<typeof getData>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Always export response types from GET routes as `Get[Feature]Response` using type inference from the data fetching function for type-safe client consumption

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/app/api/**/route.ts : Export response types from GET API routes for type-safe client use (e.g., `GetExampleResponse`)

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Infer and export response type for GET API routes using `Awaited<ReturnType<typeof functionName>>` pattern in Next.js

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication

Applied to files:

  • apps/web/app/api/fastmail/linking/auth-url/route.ts
  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Implement all server actions using the `next-safe-action` library with actionClient, actionClientUser, or adminActionClient for type safety and validation

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `next-safe-action` with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in `apps/web/utils/actions/*.validation.ts`

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.ts : Use `next-safe-action` with proper Zod validation for server actions

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.ts : Server actions should use 'use server' directive and automatically receive authentication context (`emailAccountId`) from the `actionClient`

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Create separate validation files for server actions using the naming convention `apps/web/utils/actions/NAME.validation.ts` containing Zod schemas and inferred types

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.validation.ts : Create separate Zod validation schema files for server action inputs

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
  • apps/web/utils/email/provider.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Create corresponding server action implementation files using the naming convention `apps/web/utils/actions/NAME.ts` with 'use server' directive

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `.schema()` method with Zod validation schemas from corresponding `.validation.ts` files in next-safe-action configuration

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `revalidatePath` or `revalidateTag` from 'next/cache' within server action handlers when mutations modify data displayed elsewhere

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T02:38:37.011Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1103
File: apps/web/utils/actions/rule.ts:447-457
Timestamp: 2025-12-17T02:38:37.011Z
Learning: In apps/web/utils/actions/rule.ts, revalidatePath is not needed for toggleAllRulesAction because rules data is fetched client-side using SWR, not server-side. Server-side cache revalidation is only needed when using Next.js server components or server-side data fetching.

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-12-17T11:18:06.818Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-17T11:18:06.818Z
Learning: Applies to apps/web/**/utils/actions/*.ts : Call `revalidatePath` in server actions for cache invalidation after mutations

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `adminActionClient` for actions restricted to admin users

Applied to files:

  • apps/web/utils/actions/fastmail-app-token.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/constants.ts
  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to apps/web/env.ts : Add client-side environment variables to `apps/web/env.ts` under the `client` object with `NEXT_PUBLIC_` prefix and Zod schema validation

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : Define environment variables in `apps/web/env.ts` using Zod schema validation, organizing them into `server` and `client` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't hardcode sensitive data like API keys and tokens

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:45.807Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:45.807Z
Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Always validate that resources belong to the authenticated user before any operation - use ownership checks in queries (e.g., `emailAccount: { id: emailAccountId }`) and throw `SafeError` if validation fails

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:36:43.454Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-11-25T14:36:43.454Z
Learning: Applies to apps/web/env.ts : For client-side environment variables in `apps/web/env.ts`, prefix them with `NEXT_PUBLIC_` and add them to both the `client` and `experimental__runtimeEnv` sections

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`

Applied to files:

  • apps/web/utils/fastmail/client.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/fastmail/client.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: use `withEmailAccount` for email-scoped operations, use `withAuth` for user-scoped operations, or use `withError` with proper validation for public/custom auth endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: `withEmailAccount` for email-scoped operations, `withAuth` for user-scoped operations, or `withError` with proper validation for public/cron endpoints

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Do not use try/catch blocks in GET API route handlers when using `withAuth` or `withEmailAccount` middleware, as the middleware handles error handling

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.

Applied to files:

  • apps/web/app/api/fastmail/linking/callback/route.ts
  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Design Gmail wrapper functions to be provider-agnostic to support future email providers like Outlook and ProtonMail

Applied to files:

  • apps/web/utils/email/provider.ts
  • apps/web/utils/email/provider-types.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`

Applied to files:

  • apps/web/utils/email/provider.ts
  • apps/web/utils/account-linking.ts
  • apps/web/app/(app)/accounts/AddAccount.tsx
  • apps/web/utils/oauth/account-linking.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules

Applied to files:

  • apps/web/utils/email/provider.ts
  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers

Applied to files:

  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (4)
apps/web/app/(landing)/login/LoginForm.tsx (1)
apps/web/utils/config.ts (1)
  • WELCOME_PATH (24-24)
apps/web/utils/account-linking.ts (2)
apps/web/app/api/fastmail/linking/auth-url/route.ts (1)
  • GetAuthLinkUrlResponse (14-14)
apps/web/app/api/outlook/linking/auth-url/route.ts (1)
  • GetOutlookAuthLinkUrlResponse (10-10)
apps/web/app/(app)/accounts/AddAccount.tsx (2)
apps/web/hooks/useFeatureFlags.ts (1)
  • useFastmailEnabled (19-21)
apps/web/app/(app)/accounts/FastmailAppTokenModal.tsx (1)
  • FastmailAppTokenModal (27-97)
apps/web/utils/email/fastmail.ts (9)
apps/web/utils/fastmail/client.ts (2)
  • JMAPMethodResponse (159-159)
  • FastmailClient (209-220)
apps/web/utils/email/types.ts (4)
  • EmailProvider (45-245)
  • EmailThread (7-12)
  • EmailLabel (14-25)
  • EmailSignature (38-43)
apps/web/scripts/check-enum-imports.js (1)
  • content (74-74)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (22-39)
apps/web/utils/email.ts (1)
  • isValidEmail (54-56)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (10-15)

Comment on lines +32 to +34
} else if (isFastmailProvider(provider)) {
const client = await getFastmailClientForEmail({ emailAccountId });
return new FastmailProvider(client, logger);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Pass logger parameter to getFastmailClientForEmail for consistency.

The Google and Outlook client factories (lines 27, 30) both receive the logger parameter, but the Fastmail call on line 33 omits it. For consistency with the existing pattern and to follow the guideline that helper functions should receive logger as a parameter, pass logger to getFastmailClientForEmail.

🔎 Proposed fix
   } else if (isFastmailProvider(provider)) {
-    const client = await getFastmailClientForEmail({ emailAccountId });
+    const client = await getFastmailClientForEmail({ emailAccountId, logger });
     return new FastmailProvider(client, logger);

Based on coding guidelines: Logger should be passed as a parameter to helper functions instead of creating their own logger instances.

🤖 Prompt for AI Agents
In apps/web/utils/email/provider.ts around lines 32 to 34, the call to
getFastmailClientForEmail is missing the logger argument (unlike Google and
Outlook paths); update the call to pass the existing logger (e.g.,
getFastmailClientForEmail({ emailAccountId, logger })) and, if necessary, update
getFastmailClientForEmail’s signature to accept and use the logger parameter so
helper functions consistently receive the logger.

- Extract email address from "Name <email>" format for JMAP to field
  using extractEmailAddress utility to prevent delivery failures
- Preserve sender display name in JMAP to field for better formatting
- Fix O(n²) base64 encoding loop with efficient Buffer.from().toString()
- Add Authelia env vars to turbo.json build.env for proper tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/web/utils/email/fastmail.ts (3)

1-12: Optional: Remove unused type imports.

JMAPMethodCall and JMAPError are imported but not used in this file. Consider removing them to keep imports clean.

🔎 Proposed cleanup
 import type {
   FastmailClient,
-  JMAPMethodCall,
   JMAPMethodResponse,
-  JMAPError,
 } from "@/utils/fastmail/client";
 import {
   getAccessTokenFromClient,
-  getJMAPError,
   isJMAPError,
 } from "@/utils/fastmail/client";

1120-1128: Move inbox lookup outside the loop.

The inbox mailbox lookup on line 1127 is inside the sender iteration loop. While the mailbox cache makes subsequent calls fast, it's cleaner and more efficient to fetch it once before the loop starts.

🔎 Proposed refactor
+    const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
+    if (!inbox) {
+      log.warn("Inbox mailbox not found");
+      return;
+    }
+
     for (const sender of fromEmails) {
       // Safety check: only process valid email addresses to prevent accidental bulk operations
       if (!isValidEmail(sender)) {
         log.warn("Skipping invalid sender email for bulk archive", { sender });
         continue;
       }
 
-      const inbox = await this.getMailboxByRole(FastmailMailbox.INBOX);
-      if (!inbox) continue;
-
       // Query for all emails from this sender in inbox

1957-1966: Consider batching label removals into a single API call.

This method makes sequential API calls (one per label). For better performance when removing multiple labels, consider batching the updates into a single Email/set request.

🔎 Proposed refactor to batch updates
   async removeThreadLabels(
     threadId: string,
     labelIds: string[],
   ): Promise<void> {
     if (labelIds.length === 0) return;
 
-    for (const labelId of labelIds) {
-      await this.removeThreadLabel(threadId, labelId);
-    }
+    // Get all emails in the thread
+    const threadResponse = await this.client.request([
+      [
+        "Email/query",
+        {
+          accountId: this.client.accountId,
+          filter: { inThread: threadId },
+        },
+        "0",
+      ],
+    ]);
+
+    const emailIds = getResponseData<JMAPQueryResponse>(
+      threadResponse.methodResponses[0],
+    ).ids;
+
+    if (emailIds.length === 0) return;
+
+    // Remove all labels from all emails in one request
+    const update: Record<string, Record<string, boolean | null>> = {};
+    for (const emailId of emailIds) {
+      const patches: Record<string, boolean | null> = {};
+      for (const labelId of labelIds) {
+        patches[`mailboxIds/${labelId}`] = null;
+      }
+      update[emailId] = patches;
+    }
+
+    await this.client.request([
+      [
+        "Email/set",
+        {
+          accountId: this.client.accountId,
+          update,
+        },
+        "0",
+      ],
+    ]);
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd68c01 and 9fb10b9.

📒 Files selected for processing (2)
  • apps/web/utils/email/fastmail.ts (1 hunks)
  • turbo.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • turbo.json
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for error handling in async operations

**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

**/*.{ts,tsx}: For early access feature flags, create hooks using the naming convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g., inbox-cleaner, pricing-options-2)
Always define types for A/B test variant flags (e.g., type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/email/fastmail.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx}

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

Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use @/ path aliases for imports from project root
Follow tailwindcss patterns with prettier-plugin-tailwindcss class organization
Prefix client-side environment variables with NEXT_PUBLIC_
Leverage TypeScript inference for better developer experience with type exports from API routes

Files:

  • apps/web/utils/email/fastmail.ts
**/*.ts

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

**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma's select option. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. All findUnique/findFirst calls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
All findMany queries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g., emailAccount: { id: emailAccountId }) to validate ownership

Files:

  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/email/fastmail.ts
**/*.{tsx,ts,css}

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

Implement responsive design with Tailwind CSS using a mobile-first approach

Files:

  • apps/web/utils/email/fastmail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop on <th> 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
Use semantic elements instead of role attributes in JSX
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 AR...

Files:

  • apps/web/utils/email/fastmail.ts
!(pages/_document).{jsx,tsx}

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

Don't use the next/head module in pages/_document.js on Next.js projects

Files:

  • apps/web/utils/email/fastmail.ts
**/*.{js,ts,jsx,tsx}

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

**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g., import groupBy from 'lodash/groupBy')

Files:

  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx}

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

apps/web/**/*.{ts,tsx,js,jsx}: Use proper error handling with try/catch blocks
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top

Files:

  • apps/web/utils/email/fastmail.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css,md}

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

Format code with Prettier

Files:

  • apps/web/utils/email/fastmail.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}

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

Logger should be passed as a parameter to helper functions instead of creating their own logger instances

Files:

  • apps/web/utils/email/fastmail.ts
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure async functions actually use await

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure Promise-like statements are handled appropriately

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules

Applied to files:

  • apps/web/utils/email/fastmail.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers

Applied to files:

  • apps/web/utils/email/fastmail.ts
🧬 Code graph analysis (1)
apps/web/utils/email/fastmail.ts (8)
apps/web/utils/fastmail/client.ts (3)
  • JMAPMethodResponse (159-159)
  • FastmailClient (209-220)
  • getAccessTokenFromClient (463-465)
apps/web/utils/logger.ts (2)
  • Logger (5-5)
  • createScopedLogger (17-81)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/fastmail/constants.ts (1)
  • FastmailMailbox (22-39)
apps/web/utils/email.ts (3)
  • isValidEmail (54-56)
  • extractEmailAddress (19-52)
  • extractNameFromEmail (9-16)
apps/web/utils/label.ts (1)
  • InboxZeroLabel (63-63)
apps/web/app/api/threads/validation.ts (1)
  • ThreadsQuery (15-15)
apps/web/utils/outlook/folders.ts (1)
  • OutlookFolder (10-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer


try {
// Exchange code for tokens
const tokenResponse = await fetch(FASTMAIL_OAUTH_TOKEN_URL, {
Copy link
Author

Choose a reason for hiding this comment

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

Note: Fastmail oAuth integration hasnt been tested yet, as I cant create an oAuth client myself directly, need to contact Fastmail support for that. So all changes were tested with an API KEY instead

@marcodejongh
Copy link
Author

marcodejongh commented Dec 19, 2025

Started implementing pollign and applying of filters here: marcodejongh#2. Had to create that PR in my fork so it can target this branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants