Conversation
* add Microsoft Graph * abstract API and add Outlook REST support * lay threads endpoint groundwork * add provider-based permission checking * add Outlook utils and fix README * some mail format tests based on gmail
|
@WhiteSoxx is attempting to deploy a commit to the Inbox Zero Team on Vercel. A member of the Team first needs to authorize it. |
|
Hi, I’m Jit, a friendly security platform designed to help developers build secure applications from day zero with an MVS (Minimal viable security) mindset. In case there are security findings, they will be communicated to you as a comment inside the PR. Hope you’ll enjoy using Jit. Questions? Comments? Want to learn more? Get in touch with us. |
WalkthroughThis update introduces comprehensive support for Microsoft Outlook and Microsoft Entra ID (Azure OAuth) throughout the application. It adds new authentication flows, environment variable configurations, API endpoints, and utility modules for handling Outlook and Gmail operations. The README and environment files are updated to guide users through Azure OAuth setup. Numerous utility modules are added for Gmail and Outlook integration, including message, thread, label, filter, and permission management, as well as email formatting and batch operations. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WebApp
participant AuthProvider (Google/Microsoft)
participant API
participant Outlook/Gmail API
User->>WebApp: Clicks "Sign in with Google" or "Sign in with Microsoft"
WebApp->>AuthProvider: Initiates OAuth flow (provider-specific)
AuthProvider-->>WebApp: Returns OAuth token(s)
WebApp->>API: Stores tokens, links account (provider-aware)
User->>WebApp: Navigates to mail page
WebApp->>API: Requests threads (provider-aware)
API->>Outlook/Gmail API: Fetches threads via appropriate client
Outlook/Gmail API-->>API: Returns threads
API-->>WebApp: Returns threads (provider-specific format)
WebApp-->>User: Displays threads
Possibly related PRs
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
apps/web/app/(landing)/login/LoginForm.tsxOops! Something went wrong! :( ESLint: 9.24.0 ESLint couldn't find an eslint.config.(js|mjs|cjs) file. From ESLint v9.0.0, the default configuration file is now eslint.config.js. https://eslint.org/docs/latest/use/configure/migration-guide If you still have problems after following the migration guide, please stop by apps/web/app/(app)/[emailAccountId]/assistant/page.tsxOops! Something went wrong! :( ESLint: 9.24.0 ESLint couldn't find an eslint.config.(js|mjs|cjs) file. From ESLint v9.0.0, the default configuration file is now eslint.config.js. https://eslint.org/docs/latest/use/configure/migration-guide If you still have problems after following the migration guide, please stop by apps/web/app/(app)/[emailAccountId]/mail/page.tsxOops! Something went wrong! :( ESLint: 9.24.0 ESLint couldn't find an eslint.config.(js|mjs|cjs) file. From ESLint v9.0.0, the default configuration file is now eslint.config.js. https://eslint.org/docs/latest/use/configure/migration-guide If you still have problems after following the migration guide, please stop by
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 26
🔭 Outside diff range comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/page.tsx (1)
13-19: Incorrect typing & unnecessaryawaitonparams
paramsis delivered synchronously by Next.js; treating it as aPromisebreaks type-safety and adds needless micro-tasks.-export default async function AssistantPage({ - params, -}: { - params: Promise<{ emailAccountId: string }>; -}) { - const { emailAccountId } = await params; +export default async function AssistantPage({ + params, +}: { + params: { emailAccountId: string }; +}) { + const { emailAccountId } = params;Similarly,
cookies()is synchronous:- const cookieStore = await cookies(); + const cookieStore = cookies();
🧹 Nitpick comments (45)
apps/web/utils/outlook/scopes.ts (1)
3-15: Avoid redundant scopes & mark the list as immutable
"Mail.ReadWrite"already implies"Mail.Read", so requesting both is redundant noise in the consent screen.
While harmless, it unnecessarily broadens what users must approve.
Also, addingas constor an explicitreadonlyannotation prevents accidental mutation elsewhere.-export const SCOPES = [ +export const SCOPES = [ "openid", @@ - "Mail.ReadWrite", - "Mail.Send", - "Mail.ReadBasic", - "Mail.Read", + "Mail.ReadWrite", // already includes Mail.Read + "Mail.Send", + "Mail.ReadBasic", "MailboxSettings.ReadWrite", ...(env.NEXT_PUBLIC_CONTACTS_ENABLED ? ["Contacts.ReadWrite"] : []), -]; +] as const;apps/web/package.json (1)
6-6:--turbo+ 16 GB heap may back-fire on everyday dev machinesThe combo of
--turbo(still experimental) andNODE_OPTIONS=--max_old_space_size=16384forces a 16 GB heap even for tiny projects, slowing startup and blowing up laptops.Consider:
-"dev": "cross-env NODE_OPTIONS=--max_old_space_size=16384 next dev --turbo", +"dev": "cross-env NODE_OPTIONS=--max_old_space_size=4096 next dev --turbo",or drop the flag entirely until the team is confident it’s stable.
apps/web/utils/outlook/contact.ts (1)
1-20: Minor filter logic & naming mismatch
- The utility lives under
outlook/but uses Google People API – consider relocating togoogle/for clarity.filter((c) => c.person?.emailAddresses?.[0])reads the first element instead of checking.length, which is clearer.- const contacts = - res.data.results?.filter((c) => c.person?.emailAddresses?.[0]) || []; + const contacts = + res.data.results?.filter( + (c) => (c.person?.emailAddresses?.length ?? 0) > 0, + ) || [];apps/web/env.ts (1)
15-17: Ensure non-emptyMICROSOFT_ISSUERas well
MICROSOFT_ISSUERis declared withz.string().default("common"), but unlike the client-id/secret it is not constrained with.min(1).
If the variable is present in the environment but empty (MICROSOFT_ISSUER="") validation will silently accept the empty string and override the default, which is probably unintended.- MICROSOFT_ISSUER: z.string().default("common"), + MICROSOFT_ISSUER: z.string().min(1).default("common"),apps/web/utils/middleware.ts (1)
155-161: Minor: use the cloned request (authReq) for header look-upsAfter
authMiddlewarewe already haveauthReq(a clone). Reading headers from the originalreqinstead ofauthReqhas no functional impact today, but it is easy to drift if another middleware mutates headers in the future. Prefer staying on the same object for clarity.apps/web/utils/outlook/spam.ts (1)
1-17: Namespace mismatch similar to history helper
markSpamis Gmail-only but sits inutils/outlook/. Aligning file structure with provider avoids future mix-ups.apps/web/utils/outlook/attachment.ts (1)
1-7: Directory / naming mismatch – Gmail helper lives underoutlookpathThe helper is Gmail-specific yet sits in
utils/outlook. This will be confusing once the Outlook attachment helper lands.
Move it toutils/gmail/attachment.ts(or a provider-agnostic folder) and keep the Outlook equivalent beside it.README.md (3)
156-160: Minor wording – “Copy and paste”
Copy paste→Copy and pastefor clarity.- 3. Copy paste the below into the `Manually add scopes` box: + 3. Copy and paste the text below into the `Manually add scopes` box:
166-170: Duplicate numbered stepTwo consecutive steps are labelled 5. causing Markdown renderers to restart the list.
Renumber the second occurrence to 6. (or rely on1.automatic numbering).
175-175: Heading trailing colon violates MD026Remove the trailing
:to appease markdown-lint and keep headings consistent.-### Updating .env file with Azure credentials: +### Updating .env file with Azure credentialsapps/web/app/api/outlook/threads/validation.ts (1)
3-10: Tighten validation – enforce positive limit and restricttype
limitshould also have a lower bound to avoid negative / zero requests.typeappears to represent a finite set (e.g. “inbox”, “sent”). Usez.enumto catch typos.- limit: z.coerce.number().max(100).nullish(), - type: z.string().nullish(), + limit: z + .coerce.number() + .int() + .min(1) + .max(100) + .nullish(), + type: z.enum(["inbox", "sent", "drafts"]).nullish(), // adjust listThis prevents invalid queries reaching the controller layer.
apps/web/app/(landing)/login/LoginForm.tsx (1)
110-126: Independent loading state per provider
loadingis shared between Google & Microsoft flows.
Clicking one button setsloading=true, disabling the other dialog’s button even if the first flow errors or the dialog is closed.Quick fix:
-const [loading, setLoading] = useState(false); +const [googleLoading, setGoogleLoading] = useState(false); +const [microsoftLoading, setMicrosoftLoading] = useState(false);…and use the respective setter/button prop.
Keeps UX responsive and avoids a forced page refresh after a cancelled auth popup.apps/web/utils/outlook/snippet.test.ts (1)
11-15: Redundant test – identical to previous
"should return the entire string if 'On DAY' is not present"repeats the scenario covered by the first test.
Combine or remove to keep the suite concise.apps/web/app/api/outlook/threads/controller.ts (1)
14-18: Prefer template literal, avoid brittle string concatenationBuilding the request URL via five concatenations is harder to read and error-prone. Switch to a single template literal:
- const url = - "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages" + - "?" + - params.toString() + - "&$select=id,conversationId,subject,bodyPreview,from,receivedDateTime"; + const url = `https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?${params.toString()}&$select=id,conversationId,subject,bodyPreview,from,receivedDateTime`;apps/web/utils/outlook/snippet.ts (1)
4-7: Hoist and reuse regex, add case-insensitive flagThe regex is recreated on every call. Extract it once and mark it case-insensitive to handle “On Mon,” / “on Mon,” variants.
- try { - const regex = /On (Mon|Tue|Wed|Thu|Fri|Sat|Sun),/; - const match = snippet.split(regex)[0]; + const RE_REPLY_PREFIX = /on (Mon|Tue|Wed|Thu|Fri|Sat|Sun),/i; + try { + const match = snippet.split(RE_REPLY_PREFIX)[0];apps/web/utils/outlook/watch.ts (1)
5-16: Surface API errors instead of silently returningres.data
gmail.users.watchcan fail (invalid topic, insufficient scopes, etc.). Returningres.datahides that. Throw whenres.status !== 200to bubble the error:- const res = await gmail.users.watch({ ... }); - - return res.data; + const res = await gmail.users.watch({ ... }); + if (res.status !== 200) { + throw new Error( + `Gmail watch failed: ${res.status} ${JSON.stringify(res.data)}` + ); + } + return res.data;apps/web/utils/outlook/retry.ts (1)
4-4: Logger scope is misleadingThe utility lives in
utils/outlookbut the scoped logger string is"gmail-retry". Rename the scope or move the file to avoid future confusion when grepping logs.apps/web/utils/outlook/signature.ts (2)
1-2: Heavyweight dependency loaded eagerly on every import
jsdompulls in ~1 MB of code and a few native addons. Importing it at module-load time will bundle it into every edge/server function that merely imports this file, even ifextractGmailSignatureis never called.
To keep lambdas slim, lazy-load it inside the function:-import { JSDOM } from "jsdom"; +let JSDOM: typeof import("jsdom").JSDOM;and inside the
tryblock:if (!JSDOM) ({ JSDOM } = await import("jsdom"));
30-32: Use project logger instead ofconsole.errorEvery other util pipes errors through
createScopedLogger; usingconsolebreaks log aggregation and tracing. Replace with the scoped logger for consistency.apps/web/utils/outlook/batch.ts (1)
17-21: Throw early for zero IDs is fine, but returning[]hides misuseSilently returning an empty array on
!ids.lengthcan mask a logic bug upstream. Prefer throwing or logging so the caller knows it made a pointless network trip.apps/web/utils/outlook/constants.ts (1)
16-16: Constant belongs to Google auth yet sits in Outlook util
GOOGLE_LINKING_STATE_COOKIE_NAMEis unrelated to Outlook; consider moving it to a more genericauthconstants file to avoid circular deps later.apps/web/app/api/outlook/threads/route.ts (1)
26-27:emailAccountIdretrieved but unusedThe variable is never referenced; remove it or pass it to the controller to avoid dead code and satisfy the linter.
apps/web/utils/outlook/draft.ts (2)
9-18: Add explicit return type & null-guard the draft messageExplicitly typing the function helps downstream callers and avoids accidental
any.
response.data.messagecan beundefined; access without a guard will throw.-export async function getDraft(draftId: string, gmail: gmail_v1.Gmail) { +export async function getDraft( + draftId: string, + gmail: gmail_v1.Gmail, +): Promise<ParsedMessage | null> { … - const message = parseMessage(response.data.message as MessageWithPayload); + const raw = response.data.message as MessageWithPayload | undefined; + if (!raw) return null; + const message = parseMessage(raw); return message; }
1-8: Gmail-specific utility lives in anoutlook/folderPlacing Gmail helpers under
utils/outlookis misleading and will confuse future maintainers.
Consider moving this file (and similar Gmail helpers) toutils/gmail/or a provider-agnostic location.apps/web/utils/outlook/settings.ts (1)
1-14: File location/naming is misleadingThe module only touches Gmail settings yet resides in
utils/outlook.
Please relocate (e.g.utils/gmail/settings.ts) or rename the folder to something provider-agnostic.apps/web/utils/outlook/permissions.ts (2)
1-5: Gmail permissions checker placed insideoutlookfolderThe directory hierarchy no longer reflects the content, making navigation harder.
Move toutils/gmail/permissions.tsor a genericutils/permissions/namespace.
90-97: Token clean-up targetsAccountonlyIf the Gmail refresh/access tokens are duplicated on the related
emailAccountrecord, those should be cleared too to avoid orphaned credentials.apps/web/utils/outlook/trash.ts (1)
1-5: Gmail trash helper located underoutlook/As with the other files, please move to a Gmail-specific directory to keep provider code isolated and discoverable.
apps/web/utils/outlook/forward.ts (2)
4-6: Duplicate “Fwd:” prefixes
forwardEmailSubjectalways prepends"Fwd:"even if the subject already starts with it, leading toFwd: Fwd:chains.-export const forwardEmailSubject = (subject: string) => { - return `Fwd: ${subject}`; +export const forwardEmailSubject = (subject: string) => + subject.trim().toLowerCase().startsWith("fwd:") + ? subject + : `Fwd: ${subject}`;
1-3: File location inconsistent with providerThe helper relies on Gmail-specific formatting utilities but lives in an
outlookdirectory.
Re-evaluate folder structure to avoid cross-provider coupling.apps/web/utils/outlook/forward.test.ts (1)
54-85: Test may be too brittle – asserts entire HTML blobAsserting the full HTML string (incl. whitespace + exact attribute order) makes the test fragile: a harmless re-indent or attribute re-ordering will break it.
Prefer checking only the semantics you care about (e.g. headers, presence ofgmail_quote, embedded original HTML).expect(html).toContain("---------- Forwarded message ----------"); expect(html).toContain("from@demo.com"); expect(html).toContain(message.textHtml);This keeps the test valuable while reducing maintenance overhead.
apps/web/utils/outlook/reply.ts (2)
34-35:contentHtmlmay be empty, resulting in<div dir="ltr"></div>When only
htmlContentis omitted andtextContentis undefined,contentHtmlresolves to"", leading to an empty wrapper element inside the final HTML.
Consider short-circuiting the wrapper if no new content was provided.
60-69: Hour is not zero-padded informatEmailDateMinutes are padded, hours are not. For 2 a.m. this yields
2:05, deviating from Gmail’s02:05.-const hour = date.getHours(); +const hour = date.getHours().toString().padStart(2, "0");apps/web/utils/outlook/filter.ts (2)
1-3: Module sits underoutlook/but contains Gmail-specific helpersEverything here is Gmail API–specific (
@googleapis/gmail,GmailLabel).
Relocate toutils/gmail/(or rename) to avoid provider confusion and accidental imports.
64-67:isFilterExistsErrorbrittle – relies on nestederrors[0].messageGoogle may return
error.messagewithout the nested array. Safer:const msg = (error as any)?.message ?? (error as any)?.errors?.[0]?.message; return msg === "Filter already exists";apps/web/utils/outlook/mail.ts (1)
63-75: Recipient splitting only handles,delimiterUsers often paste
;-separated address lists (Outlook default).
Consider splitting on/[,;]\s*/to be more forgiving.apps/web/app/(app)/[emailAccountId]/mail/page.tsx (2)
50-60:getKeybuilds URLSearchParams from object containingundefinedvaluesAlthough
new URLSearchParams({...})silently dropsundefined, it still keeps empty strings created later (e.g."cc=").
Filter falsy keys first for predictable URLs:const queryParams = new URLSearchParams( Object.entries(query) .filter(([, v]) => v !== undefined && v !== "") .map(([k, v]) => [k, String(v)]), );
112-113: Permissions check gated by provider but comment says Gmail-onlyThe run-time guard is correct, but the preceding comment is outdated (“provider !== outlook”).
Update to avoid confusion.apps/web/utils/outlook/client.ts (1)
48-64: Logger scope & comments reference Gmail
createScopedLogger("gmail/client")and inline comments still mention Gmail, reinforcing the mis-placement.Update scope & comments once the correct Outlook logic is in place to avoid future confusion.
apps/web/utils/outlook/message.ts (1)
72-73: Mismatch between limit check and error text
if (messageIds.length > 100)contradicts error text “Max 1000”. Adjust one of them to avoid developer confusion.apps/web/utils/auth.ts (2)
46-66: Hard-coded scope list – ignore imported constant
MICROSOFT_SCOPESis imported but a literal array is used. Prefer the constant to keep scopes in one place:- scope: [ - "openid", … "MailboxSettings.ReadWrite", - ].join(" "), + scope: MICROSOFT_SCOPES.join(" "),
404-405: Implicitanytype
let response;infersany, defeating TypeScript safety. Declare an explicit type, e.g.:let response: Response;or narrow further (
Promise<Response>etc.).apps/web/utils/outlook/label.ts (1)
18-34: Path / namespace mismatchAll exports here manipulate Gmail labels, yet the module sits under
outlook/. Consider relocating togmail/label.tsto avoid cognitive overhead when onboarding new contributors.apps/web/utils/outlook/thread.ts (2)
175-180: UnusedemailAccountIdparameter.
emailAccountIdis declared in the interface but never referenced ingetOutlookThread, creating dead code and misleading intent.-interface GetOutlookThreadParams { - id: string; - includeDrafts?: boolean; - graphClient: any; - emailAccountId: string; -} +interface GetOutlookThreadParams { + id: string; + includeDrafts?: boolean; + graphClient: any; +}and update the call signature accordingly.
199-206: Minor performance nit – useflatMapinstead of manual push.- let messages: any[] = []; - for (const thread of threads) { - if (Array.isArray(thread.posts)) { - messages = messages.concat(thread.posts); - } - } + const messages = threads.flatMap((t: any) => + Array.isArray(t.posts) ? t.posts : [], + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
apps/web/public/images/microsoft.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (41)
README.md(2 hunks)apps/web/.env.example(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/page.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/mail/page.tsx(3 hunks)apps/web/app/(landing)/login/LoginForm.tsx(1 hunks)apps/web/app/api/outlook/threads/controller.ts(1 hunks)apps/web/app/api/outlook/threads/route.ts(1 hunks)apps/web/app/api/outlook/threads/validation.ts(1 hunks)apps/web/env.ts(1 hunks)apps/web/package.json(2 hunks)apps/web/utils/account.ts(2 hunks)apps/web/utils/auth.ts(11 hunks)apps/web/utils/middleware.ts(1 hunks)apps/web/utils/outlook/attachment.ts(1 hunks)apps/web/utils/outlook/batch.ts(1 hunks)apps/web/utils/outlook/client.ts(1 hunks)apps/web/utils/outlook/constants.ts(1 hunks)apps/web/utils/outlook/contact.ts(1 hunks)apps/web/utils/outlook/decode.ts(1 hunks)apps/web/utils/outlook/draft.ts(1 hunks)apps/web/utils/outlook/filter.ts(1 hunks)apps/web/utils/outlook/forward.test.ts(1 hunks)apps/web/utils/outlook/forward.ts(1 hunks)apps/web/utils/outlook/history.ts(1 hunks)apps/web/utils/outlook/label.ts(1 hunks)apps/web/utils/outlook/mail.ts(1 hunks)apps/web/utils/outlook/message.ts(1 hunks)apps/web/utils/outlook/permissions.ts(1 hunks)apps/web/utils/outlook/reply.test.ts(1 hunks)apps/web/utils/outlook/reply.ts(1 hunks)apps/web/utils/outlook/retry.ts(1 hunks)apps/web/utils/outlook/scopes.ts(1 hunks)apps/web/utils/outlook/settings.ts(1 hunks)apps/web/utils/outlook/signature.test.ts(1 hunks)apps/web/utils/outlook/signature.ts(1 hunks)apps/web/utils/outlook/snippet.test.ts(1 hunks)apps/web/utils/outlook/snippet.ts(1 hunks)apps/web/utils/outlook/spam.ts(1 hunks)apps/web/utils/outlook/thread.ts(1 hunks)apps/web/utils/outlook/trash.ts(1 hunks)apps/web/utils/outlook/watch.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (18)
apps/web/utils/middleware.ts (1)
apps/web/utils/config.ts (1)
EMAIL_ACCOUNT_HEADER(3-3)
apps/web/utils/outlook/watch.ts (2)
apps/web/utils/outlook/label.ts (1)
GmailLabel(20-34)apps/web/env.ts (1)
env(5-206)
apps/web/app/(landing)/login/LoginForm.tsx (2)
apps/web/components/ui/dialog.tsx (5)
Dialog(112-112)DialogTrigger(116-116)DialogContent(117-117)DialogHeader(118-118)DialogTitle(120-120)apps/web/components/Typography.tsx (1)
SectionDescription(114-114)
apps/web/utils/outlook/spam.ts (1)
apps/web/utils/outlook/label.ts (1)
GmailLabel(20-34)
apps/web/utils/outlook/batch.ts (1)
apps/web/utils/types.ts (1)
isDefined(8-10)
apps/web/app/api/outlook/threads/controller.ts (1)
apps/web/app/api/outlook/threads/validation.ts (1)
ThreadsQuery(11-11)
apps/web/utils/outlook/scopes.ts (1)
apps/web/env.ts (1)
env(5-206)
apps/web/app/api/outlook/threads/route.ts (3)
apps/web/utils/middleware.ts (1)
withEmailAccount(194-198)apps/web/app/api/outlook/threads/validation.ts (1)
threadsQuery(3-10)apps/web/app/api/outlook/threads/controller.ts (1)
getOutlookThreads(3-26)
apps/web/utils/outlook/signature.test.ts (1)
apps/web/utils/outlook/signature.ts (1)
extractGmailSignature(11-35)
apps/web/utils/outlook/reply.ts (1)
apps/web/utils/types.ts (1)
ParsedMessage(47-58)
apps/web/utils/outlook/snippet.test.ts (1)
apps/web/utils/outlook/snippet.ts (1)
snippetRemoveReply(2-11)
apps/web/utils/account.ts (2)
apps/web/utils/outlook/client.ts (2)
getAccessTokenFromClient(112-117)getGmailClientWithRefresh(49-98)apps/web/utils/gmail/client.ts (2)
getAccessTokenFromClient(112-117)getGmailClientWithRefresh(49-98)
apps/web/utils/outlook/draft.ts (4)
apps/web/utils/logger.ts (1)
createScopedLogger(17-65)apps/web/utils/mail.ts (1)
parseMessage(14-16)apps/web/utils/types.ts (1)
MessageWithPayload(39-41)apps/web/utils/error.ts (1)
isGmailError(37-46)
apps/web/utils/outlook/filter.ts (1)
apps/web/utils/outlook/label.ts (1)
GmailLabel(20-34)
apps/web/app/(app)/[emailAccountId]/mail/page.tsx (3)
apps/web/app/api/outlook/threads/validation.ts (1)
ThreadsQuery(11-11)apps/web/app/api/google/threads/controller.ts (1)
ThreadsResponse(16-16)apps/web/app/(app)/[emailAccountId]/PermissionsCheck.tsx (1)
PermissionsCheck(11-28)
apps/web/utils/outlook/mail.ts (3)
apps/web/app/api/google/webhook/logger.ts (1)
logger(3-3)apps/web/utils/logger.ts (1)
createScopedLogger(17-65)apps/web/utils/mail.ts (1)
convertEmailHtmlToText(143-157)
apps/web/utils/outlook/message.ts (6)
apps/web/utils/logger.ts (1)
createScopedLogger(17-65)apps/web/utils/types.ts (5)
MessageWithPayload(39-41)ParsedMessage(47-58)BatchError(24-31)isBatchError(33-37)isDefined(8-10)apps/web/utils/gmail/batch.ts (1)
getBatch(10-44)apps/web/utils/mail.ts (1)
parseMessage(14-16)apps/web/utils/email.ts (1)
extractDomainFromEmail(53-71)apps/web/utils/outlook/client.ts (1)
getAccessTokenFromClient(112-117)
apps/web/utils/outlook/thread.ts (4)
apps/web/utils/types.ts (3)
ThreadWithPayloadMessages(43-45)isDefined(8-10)MessageWithPayload(39-41)apps/web/utils/gmail/batch.ts (1)
getBatch(10-44)apps/web/utils/mail.ts (2)
parseMessages(24-51)parseMessage(14-16)apps/web/utils/outlook/label.ts (1)
GmailLabel(20-34)
🪛 LanguageTool
README.md
[uncategorized] ~134-~134: Possible missing preposition found.
Context: ...4. Enter your contact information 5. Agree to the User Data policy and then click ...
(AI_HYDRA_LEO_MISSING_TO)
[duplication] ~140-~140: Possible typo: you repeated a word.
Context: ...Client ID. 2. In Application Type, Choose Web application 3. Choose a name for your web client 4. In Aut...
(ENGLISH_WORD_REPEAT_RULE)
[grammar] ~156-~156: Did you mean “Copy and paste”?
Context: ... 2. Click Add or remove scopes 3. Copy paste the below into the `Manually add scopes...
(COPY_PASTE)
[duplication] ~166-~166: Possible typo: you repeated a word.
Context: ...gleapis.com/auth/contacts ``` 4. Click Update 5. Click `Save` in the Data Access page. 5. Add...
(ENGLISH_WORD_REPEAT_RULE)
[style] ~209-~209: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...f the client secret you just created. - Set MICROSOFT_ISSUER to your **Directory ...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🪛 markdownlint-cli2 (0.17.2)
README.md
175-175: Trailing punctuation in heading
Punctuation: ':'
(MD026, no-trailing-punctuation)
🪛 Biome (1.9.4)
apps/web/app/api/outlook/threads/controller.ts
[error] 15-18: Template literals are preferred over string concatenation.
Unsafe fix: Use a template literal.
(lint/style/useTemplate)
apps/web/utils/auth.ts
[error] 144-145: Do not use template literals if interpolation and special-character handling are not needed.
Unsafe fix: Replace with string literal
(lint/style/noUnusedTemplateLiteral)
[error] 404-405: This variable implicitly has the any type.
Variable declarations without type annotation and initialization implicitly have the any type. Declare a type or initialize the variable with some value.
(lint/suspicious/noImplicitAnyLet)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: Static Code Analysis Js
- GitHub Check: Jit Security
🔇 Additional comments (11)
apps/web/package.json (1)
37-37: New dependency added – verify tree-shaking & bundle size
@microsoft/microsoft-graph-client(~300 kB min+gzip) lands in the client bundle if imported in React components. Double-check imports stay server-side / API-side only, or leverage dynamicimport()to keep the main bundle slim.apps/web/.env.example (1)
13-15: ** 👍 Helpful placeholders added**The Microsoft OAuth variables are clearly named and positioned next to the Gmail block—nice touch.
apps/web/utils/outlook/decode.ts (1)
3-5: Looks good – concise and covers invisible charsThe helper cleanly decodes HTML entities and strips zero-width characters. 👍
apps/web/app/api/outlook/threads/controller.ts (1)
11-13: Verify correct pagination parameter – Graph may expect$skiptokenGraph’s message listing typically supports
$skiptokenfor paging large result sets, whereas$skipis an OData offset that is not always enabled. Ensure the API really honours$skip; otherwise switch to$skiptoken(the value returned in@odata.nextLink).apps/web/utils/outlook/reply.test.ts (1)
1-3: Path / folder mismatch may confuse future maintainersThe test sits in
utils/outlookyet imports the implementation from"@/utils/gmail/reply".
If the reply helper is generic, consider moving it to a provider-agnostic folder; otherwise rename either the test file or the import to keep Outlook-specific code together.apps/web/utils/outlook/signature.test.ts (1)
4-68: Comprehensive and readable test coverage – nice workThe suite exercises multiple edge cases (nested signatures, entities, absence of signature, empty input). Assertions are clear and deterministic.
apps/web/utils/outlook/signature.ts (1)
23-28: Whitespace normalisation may corrupt inline HTMLThe regex
\s+<removes all spaces before a<, which also strips significant spaces inside attribute values (<span title="2 < 3">). Consider limiting this clean-up to text nodes or using an HTML serializer.apps/web/app/api/outlook/threads/route.ts (1)
18-19: Access-token lookup fails whensession.useris undefined
session.usercan beundefined(e.g., when usingjwtsessions). Guard or narrow the type before optional chaining:const accessToken = (session as any).accessToken ?? (session.user && (session.user as any).accessToken);apps/web/utils/account.ts (1)
119-128: ```bash
#!/bin/bashInspect the implementation of getOutlookClientWithRefresh
rg -n "getOutlookClientWithRefresh" -C10 apps/web/utils/outlook/client.ts
</details> <details> <summary>apps/web/utils/outlook/thread.ts (2)</summary> `8-10`: **Possible wrong import path for `GmailLabel`.** The enum lives under `utils/outlook/label.ts` (see provided snippet) but is imported from `utils/gmail/label`. If the latter file does not exist or does not re-export `GmailLabel`, this will break compilation at runtime. ```diff -import { GmailLabel } from "@/utils/gmail/label"; +import { GmailLabel } from "@/utils/outlook/label";Please verify the correct location or add a barrel export.
182-221: ```shell
#!/bin/bashSearch for pagination usage or nextLink in thread.ts
rg --color never -n "@odata.nextLink" -n "nextLink" -n "while" -n "for" apps/web/utils/outlook/thread.ts
Search for expand posts usage
rg --color never -n "expand("posts"" apps/web/utils/outlook/thread.ts
Search for draft detection patterns
rg --color never -n "isDraft" -n "messageType" -n "Sent" apps/web/utils/outlook/thread.ts
Search for error handling around graphClient uses
rg --color never -n "try" -n "catch" -n "finally" apps/web/utils/outlook/thread.ts
</details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| // FIXME: redirected too many times | ||
| // if (!hasRule) { | ||
| // redirect(prefixPath(emailAccountId, "/assistant?onboarding=true")); | ||
| // } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Commented-out redirect leaves UX edge case unresolved
The “too many redirects” loop is masked by commenting code out, but new users without a rule now silently skip onboarding. Either:
- Fix the redirect condition, or
- Add a TODO with a tracking issue reference so this doesn’t ship unnoticed.
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/page.tsx around lines 31 to 34,
the redirect code is commented out to avoid a "too many redirects" loop, but
this causes new users without a rule to skip onboarding silently. To fix this,
either correct the redirect condition to prevent the loop or add a TODO comment
referencing a tracking issue to ensure this UX edge case is addressed before
shipping.
| let emailAccountId = req.headers.get(EMAIL_ACCOUNT_HEADER); | ||
|
|
||
| if (!emailAccountId) { | ||
| const url = new URL(req.url); | ||
| emailAccountId = url.searchParams.get("emailAccountId") ?? undefined; | ||
| } | ||
| if (!emailAccountId) { |
There was a problem hiding this comment.
Query-param name may not match new endpoint – risk of false 403
emailAccountId = url.searchParams.get("emailAccountId") only recognises the exact key emailAccountId.
According to the PR description the new endpoint uses ?emailAccount=…. A mismatch will make every call fall through to the 403 branch even when the parameter is supplied.
Recommend accepting both variants (or, preferably, standardise the public contract):
- emailAccountId = url.searchParams.get("emailAccountId") ?? undefined;
+ emailAccountId =
+ url.searchParams.get("emailAccountId") ??
+ url.searchParams.get("emailAccount") ??
+ undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let emailAccountId = req.headers.get(EMAIL_ACCOUNT_HEADER); | |
| if (!emailAccountId) { | |
| const url = new URL(req.url); | |
| emailAccountId = url.searchParams.get("emailAccountId") ?? undefined; | |
| } | |
| if (!emailAccountId) { | |
| let emailAccountId = req.headers.get(EMAIL_ACCOUNT_HEADER); | |
| if (!emailAccountId) { | |
| const url = new URL(req.url); | |
| emailAccountId = | |
| url.searchParams.get("emailAccountId") ?? | |
| url.searchParams.get("emailAccount") ?? | |
| undefined; | |
| } | |
| if (!emailAccountId) { |
🤖 Prompt for AI Agents
In apps/web/utils/middleware.ts around lines 155 to 161, the code only checks
for the query parameter "emailAccountId" but the new endpoint uses
"emailAccount". Update the code to check for both "emailAccountId" and
"emailAccount" query parameters from the URL search params, assigning whichever
is present to emailAccountId. This will prevent false 403 errors when the
parameter is supplied under the new name.
| import type { gmail_v1 } from "@googleapis/gmail"; | ||
|
|
||
| export async function getHistory( | ||
| gmail: gmail_v1.Gmail, | ||
| options: { | ||
| startHistoryId: string; | ||
| historyTypes?: string[]; | ||
| maxResults?: number; | ||
| }, | ||
| ) { | ||
| const history = await gmail.users.history.list({ | ||
| userId: "me", | ||
| startHistoryId: options.startHistoryId, | ||
| historyTypes: options.historyTypes, | ||
| maxResults: options.maxResults, | ||
| }); | ||
|
|
||
| return history.data; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Gmail helper lives under an outlook path – confusing placement
The function is Gmail-specific (gmail.users.history.list) yet the file resides in utils/outlook/. This makes code navigation harder and contradicts the new Outlook-centric modules added in the PR.
Consider relocating to utils/gmail/history.ts (or a provider-agnostic namespace) to keep responsibilities clear.
🤖 Prompt for AI Agents
In apps/web/utils/outlook/history.ts lines 1 to 19, the Gmail-specific helper
function is misplaced under an Outlook directory, causing confusion. Move this
file to a more appropriate location such as apps/web/utils/gmail/history.ts or a
shared provider-agnostic folder to clearly separate Gmail-related code from
Outlook modules and improve code organization.
| const attachment = await gmail.users.messages.attachments.get({ | ||
| userId: "me", | ||
| id: attachmentId, | ||
| messageId, | ||
| }); | ||
| const attachmentData = attachment.data; | ||
| return attachmentData; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add defensive error handling & return the decoded payload only
attachments.get can throw (token expiry, 404, etc.) and attachment.data still contains wrapper metadata. Consider:
- const attachment = await gmail.users.messages.attachments.get({
- userId: "me",
- id: attachmentId,
- messageId,
- });
- const attachmentData = attachment.data;
- return attachmentData;
+ try {
+ const { data } = await gmail.users.messages.attachments.get({
+ userId: "me",
+ id: attachmentId,
+ messageId,
+ });
+ // `data.data` is the base64 payload – surface that directly
+ return data.data ?? "";
+ } catch (err) {
+ // propagate a clean, provider-agnostic error
+ throw new Error(`Failed to fetch Gmail attachment ${attachmentId}: ${String(err)}`);
+ }This keeps callers provider-agnostic and prevents them from parsing Google’s response shape.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const attachment = await gmail.users.messages.attachments.get({ | |
| userId: "me", | |
| id: attachmentId, | |
| messageId, | |
| }); | |
| const attachmentData = attachment.data; | |
| return attachmentData; | |
| try { | |
| const { data } = await gmail.users.messages.attachments.get({ | |
| userId: "me", | |
| id: attachmentId, | |
| messageId, | |
| }); | |
| // `data.data` is the base64 payload – surface that directly | |
| return data.data ?? ""; | |
| } catch (err) { | |
| // propagate a clean, provider-agnostic error | |
| throw new Error(`Failed to fetch Gmail attachment ${attachmentId}: ${String(err)}`); | |
| } |
🤖 Prompt for AI Agents
In apps/web/utils/outlook/attachment.ts around lines 8 to 14, add a try-catch
block around the call to gmail.users.messages.attachments.get to handle
potential errors like token expiry or 404. In the catch block, handle or rethrow
the error appropriately. Also, decode the attachment data payload (e.g., base64
decode) before returning it, so callers receive only the raw decoded content
without Google-specific metadata, keeping the interface provider-agnostic.
| it("should return the string before 'On DAY'", () => { | ||
| const snippet = "This is a test email. On Monday, we will meet."; | ||
| const result = snippetRemoveReply(snippet); | ||
| expect(result).toBe(snippet); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Test description doesn’t match assertion
The test says it “should return the string before ‘On DAY’” but asserts the entire snippet (because “Monday” is not matched).
Either update the description or use an abbreviated day to make the assertion meaningful.
-it("should return the string before 'On DAY'", () => {
- const snippet = "This is a test email. On Monday, we will meet.";
+it("should return the string before 'On DAY (abbr)'", () => {
+ const snippet = "This is a test email. On Mon, we will meet.";
const result = snippetRemoveReply(snippet);
- expect(result).toBe(snippet);
+ expect(result).toBe("This is a test email.");
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it("should return the string before 'On DAY'", () => { | |
| const snippet = "This is a test email. On Monday, we will meet."; | |
| const result = snippetRemoveReply(snippet); | |
| expect(result).toBe(snippet); | |
| }); | |
| it("should return the string before 'On DAY (abbr)'", () => { | |
| const snippet = "This is a test email. On Mon, we will meet."; | |
| const result = snippetRemoveReply(snippet); | |
| expect(result).toBe("This is a test email."); | |
| }); |
🤖 Prompt for AI Agents
In apps/web/utils/outlook/snippet.test.ts around lines 5 to 9, the test
description states it should return the string before 'On DAY' but the assertion
checks the entire snippet because "Monday" does not match the pattern. To fix
this, either update the test description to reflect that the entire string is
expected or modify the snippet to use an abbreviated day (e.g., "Mon") so the
function correctly matches and returns the substring before 'On DAY', making the
assertion meaningful.
| export async function getOutlookClientForEmail({ | ||
| emailAccountId, | ||
| }: { | ||
| emailAccountId: string; | ||
| }) { | ||
| const tokens = await getTokens({ emailAccountId }); | ||
| const gmail = getOutlookClientWithRefresh({ | ||
| accessToken: tokens.accessToken, | ||
| refreshToken: tokens.refreshToken || "", | ||
| expiresAt: tokens.expiresAt ?? null, | ||
| emailAccountId, | ||
| }); | ||
| return gmail; | ||
| } |
There was a problem hiding this comment.
Undefined helper & misleading variable name
getOutlookClientWithRefresh is neither imported nor declared, causing a compile-time reference error.
Additionally, the returned instance is stored in a variable named gmail, which is confusing for Outlook logic.
-import { /* … */ } from "@/utils/gmail/client";
+import { getOutlookClientWithRefresh } from "@/utils/outlook/client"; // or correct path
…
- const gmail = getOutlookClientWithRefresh({ … });
- return gmail;
+ const outlook = await getOutlookClientWithRefresh({ … });
+ return outlook;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function getOutlookClientForEmail({ | |
| emailAccountId, | |
| }: { | |
| emailAccountId: string; | |
| }) { | |
| const tokens = await getTokens({ emailAccountId }); | |
| const gmail = getOutlookClientWithRefresh({ | |
| accessToken: tokens.accessToken, | |
| refreshToken: tokens.refreshToken || "", | |
| expiresAt: tokens.expiresAt ?? null, | |
| emailAccountId, | |
| }); | |
| return gmail; | |
| } | |
| -import { /* … */ } from "@/utils/gmail/client"; | |
| +import { getOutlookClientWithRefresh } from "@/utils/outlook/client"; | |
| export async function getOutlookClientForEmail({ | |
| emailAccountId, | |
| }: { | |
| emailAccountId: string; | |
| }) { | |
| const tokens = await getTokens({ emailAccountId }); | |
| - const gmail = getOutlookClientWithRefresh({ | |
| + const outlook = await getOutlookClientWithRefresh({ | |
| accessToken: tokens.accessToken, | |
| refreshToken: tokens.refreshToken || "", | |
| expiresAt: tokens.expiresAt ?? null, | |
| emailAccountId, | |
| }); | |
| - return gmail; | |
| + return outlook; | |
| } |
🤖 Prompt for AI Agents
In apps/web/utils/account.ts around lines 99 to 112, the function uses an
undefined helper getOutlookClientWithRefresh which must be imported or declared
to avoid reference errors. Also, rename the variable currently named gmail to a
more appropriate name like outlookClient to accurately reflect the Outlook
context and avoid confusion. Ensure the import statement for
getOutlookClientWithRefresh is added at the top if missing.
| import { auth, gmail, type gmail_v1 } from "@googleapis/gmail"; | ||
| import { people } from "@googleapis/people"; | ||
| import { saveTokens } from "@/utils/auth"; | ||
| import { env } from "@/env"; | ||
| import { createScopedLogger } from "@/utils/logger"; | ||
| import { SCOPES } from "@/utils/gmail/scopes"; | ||
| import { SafeError } from "@/utils/error"; | ||
|
|
||
| const logger = createScopedLogger("gmail/client"); | ||
|
|
There was a problem hiding this comment.
Module mis-named – contains Gmail implementation
This “outlook” client actually imports @googleapis/gmail, people, and uses Google scopes.
Rename the file (gmail/client.ts) or rewrite it to build and refresh Microsoft Graph tokens; otherwise Outlook-related imports in other modules cannot be resolved.
🤖 Prompt for AI Agents
In apps/web/utils/outlook/client.ts lines 1 to 10, the file is misnamed as
"outlook" but contains Gmail-specific imports and logic. Rename the file from
"outlook/client.ts" to "gmail/client.ts" to accurately reflect its Gmail
implementation and avoid confusion or import resolution issues in other modules.
| import type { gmail_v1 } from "@googleapis/gmail"; | ||
| import { parseMessage } from "@/utils/mail"; | ||
| import { | ||
| type BatchError, | ||
| type MessageWithPayload, | ||
| type ParsedMessage, | ||
| isBatchError, | ||
| isDefined, | ||
| } from "@/utils/types"; | ||
| import { getBatch } from "@/utils/gmail/batch"; | ||
| import { extractDomainFromEmail } from "@/utils/email"; | ||
| import { createScopedLogger } from "@/utils/logger"; | ||
| import { sleep } from "@/utils/sleep"; | ||
| import { getAccessTokenFromClient } from "@/utils/gmail/client"; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Gmail-specific code inside Outlook module
The file lives under outlook/ yet pulls Gmail types & helpers. Either move it to a Gmail folder or re-implement using Microsoft Graph; current location is misleading.
🤖 Prompt for AI Agents
In apps/web/utils/outlook/message.ts lines 1 to 15, the code imports
Gmail-specific types and utilities in a file located in the Outlook module,
which is misleading. To fix this, either move this file and its code to a
Gmail-specific folder to match its dependencies or refactor the implementation
to use Microsoft Graph APIs and types appropriate for Outlook, ensuring the
module's content aligns with its folder and purpose.
| const PUBLIC_DOMAINS = new Set([ | ||
| "gmail.com", | ||
| "yahoo.com", | ||
| "hotmail.com", | ||
| "outlook.com", | ||
| "aol.com", | ||
| "icloud.com", | ||
| "@me.com", | ||
| "protonmail.com", | ||
| "zoho.com", | ||
| "yandex.com", | ||
| "fastmail.com", | ||
| "gmx.com", | ||
| "@hey.com", | ||
| ]); |
There was a problem hiding this comment.
Inconsistent domain set
PUBLIC_DOMAINS mixes plain domains and entries starting with “@” which will never match the output of extractDomainFromEmail (returns domain without “@”).
Strip the @ symbols or make all entries consistent.
- "@me.com",
+ "me.com",
- "@hey.com",
+ "hey.com",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const PUBLIC_DOMAINS = new Set([ | |
| "gmail.com", | |
| "yahoo.com", | |
| "hotmail.com", | |
| "outlook.com", | |
| "aol.com", | |
| "icloud.com", | |
| "@me.com", | |
| "protonmail.com", | |
| "zoho.com", | |
| "yandex.com", | |
| "fastmail.com", | |
| "gmx.com", | |
| "@hey.com", | |
| ]); | |
| const PUBLIC_DOMAINS = new Set([ | |
| "gmail.com", | |
| "yahoo.com", | |
| "hotmail.com", | |
| "outlook.com", | |
| "aol.com", | |
| "icloud.com", | |
| "me.com", | |
| "protonmail.com", | |
| "zoho.com", | |
| "yandex.com", | |
| "fastmail.com", | |
| "gmx.com", | |
| "hey.com", | |
| ]); |
🤖 Prompt for AI Agents
In apps/web/utils/outlook/message.ts around lines 168 to 182, the PUBLIC_DOMAINS
set inconsistently includes some domains with a leading "@" symbol, which will
not match the domain strings returned by extractDomainFromEmail since it returns
domains without "@". To fix this, remove the "@" symbols from all entries in the
PUBLIC_DOMAINS set so that all domains are plain domain strings for consistent
matching.
| export async function getThreadsBatch( | ||
| threadIds: string[], | ||
| accessToken: string, | ||
| ): Promise<ThreadWithPayloadMessages[]> { | ||
| const batch = await getBatch( | ||
| threadIds, | ||
| "/gmail/v1/users/me/threads", | ||
| accessToken, | ||
| ); | ||
|
|
||
| return batch; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
getThreadsBatch can overflow the Gmail batch limit.
getBatch throws when ids.length > BATCH_LIMIT (100).
getThreadsBatch forwards threadIds unchanged, so any caller that passes >100 IDs will explode.
Consider chunking:
export async function getThreadsBatch(
threadIds: string[],
accessToken: string,
): Promise<ThreadWithPayloadMessages[]> {
- const batch = await getBatch(
- threadIds,
- "/gmail/v1/users/me/threads",
- accessToken,
- );
-
- return batch;
+ const CHUNK = 100; // Gmail batch hard-limit
+ const results: ThreadWithPayloadMessages[] = [];
+
+ for (let i = 0; i < threadIds.length; i += CHUNK) {
+ const slice = threadIds.slice(i, i + CHUNK);
+ const batch = await getBatch(
+ slice,
+ "/gmail/v1/users/me/threads",
+ accessToken,
+ );
+ results.push(...batch);
+ }
+
+ return results;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function getThreadsBatch( | |
| threadIds: string[], | |
| accessToken: string, | |
| ): Promise<ThreadWithPayloadMessages[]> { | |
| const batch = await getBatch( | |
| threadIds, | |
| "/gmail/v1/users/me/threads", | |
| accessToken, | |
| ); | |
| return batch; | |
| } | |
| export async function getThreadsBatch( | |
| threadIds: string[], | |
| accessToken: string, | |
| ): Promise<ThreadWithPayloadMessages[]> { | |
| const CHUNK = 100; // Gmail batch hard-limit | |
| const results: ThreadWithPayloadMessages[] = []; | |
| for (let i = 0; i < threadIds.length; i += CHUNK) { | |
| const slice = threadIds.slice(i, i + CHUNK); | |
| const batch = await getBatch( | |
| slice, | |
| "/gmail/v1/users/me/threads", | |
| accessToken, | |
| ); | |
| results.push(...batch); | |
| } | |
| return results; | |
| } |
🤖 Prompt for AI Agents
In apps/web/utils/outlook/thread.ts around lines 75 to 86, getThreadsBatch
forwards the entire threadIds array to getBatch, which throws an error if the
array length exceeds 100. To fix this, modify getThreadsBatch to split threadIds
into chunks of 100 or fewer, call getBatch for each chunk, and then combine all
results before returning, ensuring no batch exceeds the Gmail API limit.
|
Hey, we have a few people that all took this issue at the same time :( Being worked on here: If you have comments or think anything should be changed happy to hear. |
|
Outlook will go live soon via the other PR |
This PR adapts the API to allow users to grab threads from an Outlook account by using the /api/outlook/threads?emailAccount= endpoint.
Notes:
Related Issue:
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores