Skip to content

Comments

Default to reply all recipients#777

Merged
elie222 merged 6 commits intomainfrom
cursor/default-to-reply-all-recipients-2d23
Sep 14, 2025
Merged

Default to reply all recipients#777
elie222 merged 6 commits intomainfrom
cursor/default-to-reply-all-recipients-2d23

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Sep 14, 2025

Implement default "reply all" behavior for AI draft replies by including original TO and CC recipients in the reply's CC field.


Open in Cursor Open in Web

Summary by CodeRabbit

  • New Features

    • Unified "Reply All" recipient logic for drafts: reply-to preferred, supports overriding To.
  • Bug Fixes

    • Removes duplicate recipients, trims spaces, excludes the sender from CC, and omits CC when no valid recipients remain.
  • Tests

    • Added comprehensive tests verifying reply-all recipient construction and CC formatting.
  • Chores

    • Draft APIs updated to accept the user's email so replies correctly filter CC recipients.

Co-authored-by: eliesteinbock <eliesteinbock@gmail.com>
@cursor
Copy link

cursor bot commented Sep 14, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Sep 14, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Sep 14, 2025 1:08pm

@CLAassistant
Copy link

CLAassistant commented Sep 14, 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.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 14, 2025

Caution

Review failed

The pull request is closed.

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 a reply‑all utility and tests, and threads it through Gmail and Outlook draft flows and provider interfaces so draft creation computes To/CC using buildReplyAllRecipients and formatCcList; draftEmail signatures and call sites now accept a userEmail parameter.

Changes

Cohort / File(s) Summary
Reply‑all utilities
apps/web/utils/email/reply-all.ts
New module exporting ReplyAllRecipients, buildReplyAllRecipients(headers, overrideTo, currentUserEmail) and formatCcList(ccList). Chooses To via override → Reply‑To → From; builds deduplicated, trimmed CC from original To/CC excluding primary recipient and current user.
Reply‑all tests
apps/web/utils/email/reply-all.test.ts
New test suite exercising To precedence, Reply‑To usage, override behavior, CC construction (trimming, empty filtering), duplicate removal, excluding current user, and formatCcList outputs.
Gmail integration
apps/web/utils/gmail/mail.ts, apps/web/utils/email/google.ts
draftEmail signatures updated to accept userEmail: string; draftEmail now calls buildReplyAllRecipients and uses formatCcList to set to and cc. Gmail provider forwards userEmail to helper calls.
Outlook integration
apps/web/utils/outlook/mail.ts, apps/web/utils/email/microsoft.ts
draftEmail signatures updated to accept userEmail: string; draftEmail uses buildReplyAllRecipients and maps cc into Outlook ccRecipients objects, including CC only when present. Provider forwards userEmail.
Email types / API surface
apps/web/utils/email/types.ts
EmailProvider.draftEmail signature changed to include userEmail: string (inserted after args), shifting executedRule position.
AI actions & callers
apps/web/utils/ai/actions.ts, apps/web/utils/reply-tracker/generate-draft.ts
Actions and call sites updated to accept/forward userEmail into client.draftEmail invocations (calls now pass the extra userEmail argument).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Composer Trigger
  participant Mail as Draft Flow
  participant Util as buildReplyAllRecipients
  participant Format as formatCcList
  participant Provider as Email Provider

  UI->>Mail: draftEmail(originalEmail, args, userEmail)
  Mail->>Util: buildReplyAllRecipients(originalEmail.headers, args.to, userEmail)
  Note over Util: Determine To (override → Reply‑To → From)\nBuild CC = trimmed, deduped union of original To & CC\nExclude primary recipient and current user
  Util-->>Mail: { to, cc[] }
  Mail->>Format: formatCcList(cc[])
  Format-->>Mail: ccString | undefined
  Mail->>Provider: client.draftEmail(..., { to, cc: ccString }, userEmail)
  Provider-->>Mail: { draftId }
  Mail-->>UI: draft created
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • johnlowe399-blip

Poem

I twitch my ears at threads that sprawl,
Hop-hop—now smarter we reply-all.
Trim the tails, dedupe the crew,
CCs in line, and To’s anew.
This rabbit sends a tidy mail, hooray! 🐇✉️

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 "Default to reply all recipients" accurately and succinctly describes the PR's primary change — making AI-generated reply drafts include the original message's To and CC recipients by default. It is concise, specific, and directly related to the changeset so a teammate scanning history will understand the main intent.

📜 Recent 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 a01b857 and 7033d01.

📒 Files selected for processing (1)
  • apps/web/utils/email/reply-all.test.ts (1 hunks)

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.

Co-authored-by: eliesteinbock <eliesteinbock@gmail.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: 5

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

199-207: Draft To uses reply‑all primary; same normalization caveat

Mirror the toRecipients normalization in drafts as well.

-          address: recipients.to,
+          address: recipients.to.trim(),
📜 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 8474392 and aa05ead.

📒 Files selected for processing (4)
  • apps/web/utils/email/reply-all.test.ts (1 hunks)
  • apps/web/utils/email/reply-all.ts (1 hunks)
  • apps/web/utils/gmail/mail.ts (3 hunks)
  • apps/web/utils/outlook/mail.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
**/*.ts

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

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
apps/web/utils/**/*.ts

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

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/email/reply-all.ts
  • apps/web/utils/email/reply-all.test.ts
  • apps/web/utils/gmail/mail.ts
  • apps/web/utils/outlook/mail.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/email/reply-all.test.ts
**/*.test.{ts,js,tsx,jsx}

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

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

Files:

  • apps/web/utils/email/reply-all.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/email/reply-all.test.ts
apps/web/utils/gmail/**/*.ts

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

Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Files:

  • apps/web/utils/gmail/mail.ts
🧠 Learnings (1)
📚 Learning: 2025-08-31T12:17:19.419Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-08-31T12:17:19.419Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard cases: happy path, error handling, edge cases (empty/null), different user configs, and various input formats

Applied to files:

  • apps/web/utils/email/reply-all.test.ts
🧬 Code graph analysis (4)
apps/web/utils/email/reply-all.ts (1)
apps/web/utils/types.ts (1)
  • ParsedMessageHeaders (102-114)
apps/web/utils/email/reply-all.test.ts (2)
apps/web/utils/types.ts (1)
  • ParsedMessageHeaders (102-114)
apps/web/utils/email/reply-all.ts (2)
  • buildReplyAllRecipients (16-50)
  • formatCcList (56-58)
apps/web/utils/gmail/mail.ts (1)
apps/web/utils/email/reply-all.ts (2)
  • buildReplyAllRecipients (16-50)
  • formatCcList (56-58)
apps/web/utils/outlook/mail.ts (1)
apps/web/utils/email/reply-all.ts (1)
  • buildReplyAllRecipients (16-50)
⏰ 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 (7)
apps/web/utils/gmail/mail.ts (2)

20-20: Good abstraction: shared reply‑all utility import

Centralizing recipient logic via @/utils/email/reply-all is the right direction.


149-156: Ensure reply‑all returns bare emails and removes self from CC

  • apps/web/utils/email.ts exports extractEmailAddress (normalizes "Name a@b.com"); confirm buildReplyAllRecipients calls it (or otherwise strips display names) so Gmail and Outlook paths get bare addresses before creating the draft.
  • Exclude the current user's address from recipients.cc (use session/account email) so the user isn't self‑CC'd.
apps/web/utils/outlook/mail.ts (2)

9-9: Good reuse of shared reply‑all utility

Consistent with the Gmail path.


185-192: Drafts CC conversion mirrors reply; looks good

Conversion and conditional inclusion of ccRecipients are correct.

apps/web/utils/email/reply-all.test.ts (1)

160-176: formatCcList tests LGTM

Behavior matches the Gmail composer expectations (undefined when empty).

apps/web/utils/email/reply-all.ts (2)

56-58: LGTM

Returning undefined for empty CC aligns with callers.


21-44: Normalize addresses for dedupe — reuse repo parseEmail helper

Use the existing parseEmail helper (used in apps/web/components/EmailCell.tsx) to extract the bare email, then lowercase that value before computing replyTo and populating ccSet in apps/web/utils/email/reply-all.ts to avoid duplicates and strip display names; if parseEmail doesn't return a plain address string, call its address/value field and .toLowerCase(). Consider an RFC parser (e.g. email-addresses) only if parseEmail is insufficient.

Comment on lines 5 to 157
describe("buildReplyAllRecipients", () => {
it("should handle simple reply-all with TO and CC", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "user@company.com, colleague@company.com",
cc: "manager@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("colleague@company.com");
expect(result.cc).toHaveLength(3);
});

it("should use reply-to header when available", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
"reply-to": "noreply@example.com",
to: "user@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("noreply@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).not.toContain("noreply@example.com");
});

it("should handle no CC recipients", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "user@company.com, colleague@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("colleague@company.com");
expect(result.cc).toHaveLength(2);
});

it("should handle single recipient (no CC needed)", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "sender@example.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toHaveLength(0);
});

it("should remove duplicates from CC list", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "user@company.com, colleague@company.com",
cc: "colleague@company.com, manager@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toContain("colleague@company.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toHaveLength(3);
});

it("should handle override TO parameter", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "user@company.com",
cc: "manager@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers, "override@example.com");

expect(result.to).toBe("override@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).not.toContain("override@example.com");
});

it("should handle addresses with extra spaces", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: " user@company.com , colleague@company.com ",
cc: " manager@company.com ",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("colleague@company.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).toHaveLength(3);
});

it("should filter out empty addresses", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "user@company.com, , colleague@company.com",
cc: ", manager@company.com, ",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("colleague@company.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).toHaveLength(3);
});

it("should exclude the reply-to address from CC", () => {
const headers: ParsedMessageHeaders = {
from: "sender@example.com",
to: "sender@example.com, user@company.com",
cc: "manager@company.com",
subject: "Test",
date: "2024-01-01",
};

const result = buildReplyAllRecipients(headers);

expect(result.to).toBe("sender@example.com");
expect(result.cc).not.toContain("sender@example.com");
expect(result.cc).toContain("user@company.com");
expect(result.cc).toContain("manager@company.com");
expect(result.cc).toHaveLength(2);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Solid coverage of reply‑all scenarios

Good assertions on dedupe, trimming, override, and excluding the primary recipient. Add cases for:

  • Display name forms: "User <user@x.com>".
  • Case-insensitive dedupe: "USER@x.com" vs "user@x.com".
  • Multiple Reply‑To addresses (edge RFC case).

🏁 Script executed:

#!/bin/bash
# If you add normalization, add tests guarding it
rg -n 'describe\\("buildReplyAllRecipients"' -n

Length of output: 97


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== rg buildReplyAllRecipients =="
rg -n --hidden -S "buildReplyAllRecipients" || true
echo
echo "== rg for reply-all.test.ts =="
rg -n --hidden -S "reply-all.test.ts" || true
echo
TEST_FILE="apps/web/utils/email/reply-all.test.ts"
if [[ -f "$TEST_FILE" ]]; then
  echo "== Printing $TEST_FILE =="
  sed -n '1,240p' "$TEST_FILE"
else
  echo "== $TEST_FILE not found =="
fi
echo
echo "== Search for display-name style addresses (e.g., Name <a@b.com>) =="
rg -n --hidden -S "<[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+>" || true
echo
echo "== Search for 'reply-to' occurrences =="
rg -n --hidden -S "reply-to|replyTo|Reply-To|ReplyTo" || true

Length of output: 16310


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== reply-all.ts =="
sed -n '1,240p' apps/web/utils/email/reply-all.ts || true
echo
echo "== email.ts (helpers) =="
sed -n '1,320p' apps/web/utils/email.ts || true
echo
echo "== email.test.ts (helpers tests) =="
sed -n '1,320p' apps/web/utils/email.test.ts || true
echo
echo "== types.ts (headers) =="
sed -n '1,240p' apps/web/utils/types.ts || true
echo
echo "== Search for extractEmailAddress usages =="
rg -n --hidden -S "extractEmailAddress|extractNameFromEmail|buildReplyAllRecipients" || true

Length of output: 29571


Parse & normalize addresses in buildReplyAllRecipients; add tests for display-name, case-insensitive dedupe, and multi Reply‑To

buildReplyAllRecipients currently splits/trims raw header tokens and compares them verbatim — it doesn't parse "Name a@b.com", normalize case/dots, or handle a Reply‑To mailbox list.

  • Tests to add (apps/web/utils/email/reply-all.test.ts): a display-name address (e.g., "User user@x.com") in to/cc; case-insensitive duplicate addresses ("USER@x.com" vs "user@x.com"); and a multi-address Reply‑To (e.g., "a@x.com, b@x.com") asserting expected to/cc behavior.
  • Code changes (apps/web/utils/email/reply-all.ts): parse addresses with extractEmailAddress and compare using normalizeEmailAddress (helpers in apps/web/utils/email.ts); normalize overrideTo as well. If Reply‑To is a mailbox-list, pick the first valid mailbox for the reply target and exclude all reply‑to mailboxes from CC.
  • Verify downstream callers (gmail/outlook mail and formatCcList) still receive the expected shapes.
🤖 Prompt for AI Agents
In apps/web/utils/email/reply-all.test.ts around lines 5 to 157, the tests and
implementation assume raw token splitting but the code doesn't
parse/display-name addresses, perform case-insensitive dedupe, or treat Reply‑To
as a mailbox-list; update tests to include: a display-name address like "User
<user@x.com>" in to/cc, case-variant duplicates ("USER@x.com" vs "user@x.com"),
and a multi-address Reply‑To ("a@x.com, b@x.com") asserting that the first valid
Reply‑To becomes the to target and all reply‑to mailboxes are excluded from CC;
then in apps/web/utils/email/reply-all.ts parse addresses using
extractEmailAddress, normalize all addresses with normalizeEmailAddress
(including overrideTo), treat "reply-to" as a mailbox-list and pick the first
valid mailbox for the reply target while excluding every reply-to mailbox from
the final CC, and ensure downstream callers (gmail/outlook mail and
formatCcList) still receive the same shapes.

Comment on lines 76 to 83
// Use reply-all logic to build recipients
const recipients = buildReplyAllRecipients(message.headers);

// Convert CC addresses to Outlook format
const ccRecipients = recipients.cc.map((addr) => ({
emailAddress: { address: addr },
}));

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CC mapping is fine; ensure values are bare emails

recipients.cc.map(addr => ({ emailAddress: { address: addr } })) assumes addr is an email only. Outlook Graph rejects display‑name formats here. Please ensure upstream normalization or sanitize here.

-  const ccRecipients = recipients.cc.map((addr) => ({
-    emailAddress: { address: addr },
-  }));
+  const toOutlook = (addr: string) => addr.trim(); // assumes already bare email
+  const ccRecipients = recipients.cc.map((addr) => ({
+    emailAddress: { address: toOutlook(addr) },
+  }));

🏁 Script executed:

#!/bin/bash
# Spot any headers storing display names that could flow here
rg -nC2 -e 'headers\.(to|cc|from|reply-to).*<' --type=ts

Length of output: 618


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== outlook/mail.ts (first 240 lines) =="
if [ -f apps/web/utils/outlook/mail.ts ]; then
  nl -ba apps/web/utils/outlook/mail.ts | sed -n '1,240p'
else
  echo "MISSING: apps/web/utils/outlook/mail.ts"
fi

echo
echo "== locate buildReplyAllRecipients occurrences =="
rg -n --hidden -S "buildReplyAllRecipients" || true

echo
echo "== show files that mention buildReplyAllRecipients (first 240 lines) =="
for f in $(rg -n --hidden -S "buildReplyAllRecipients" --files); do
  echo "----- $f -----"
  sed -n '1,240p' "$f" || true
done

echo
echo "== search for display-name style uses of headers (angle-bracket) =="
rg -n --hidden -S "headers\.(to|cc|from|reply-to).*<" || true

echo
echo "== search for direct usages/assignments of message.headers.to|cc|from|reply-to =="
rg -n --hidden -S "message\.headers\.(to|cc|from|reply-to)" || true

echo
echo "== find formatFromEmailWithName / formatToEmailWithName definitions/usages =="
rg -n --hidden -S "formatFromEmailWithName|formatToEmailWithName" -C3 || true

echo
echo "== search for helpers that parse/strip display names (parseAddress|extractEmail|emailOnly|stripDisplayName) =="
rg -n --hidden -S "parseAddress|extractEmail|emailOnly|stripDisplayName|onlyEmail|addressOnly" -C3 || true

echo
echo "== search for places mapping recipients.cc or .to =="
rg -n --hidden -S "recipients\.(cc|to)\b|\.cc\.map\(|\.to\.map\(" -C3 || true

Length of output: 80


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== apps/web/utils/outlook/mail.ts (first 240 lines) =="
if [ -f apps/web/utils/outlook/mail.ts ]; then
  sed -n '1,240p' apps/web/utils/outlook/mail.ts || true
else
  echo "MISSING: apps/web/utils/outlook/mail.ts"
fi

echo
echo "== occurrences of buildReplyAllRecipients =="
rg -n --hidden -S "buildReplyAllRecipients" -C3 || true

echo
echo "== occurrences of recipients.cc/.to mapping =="
rg -n --hidden -S "recipients\.(cc|to)\b|\.cc\.map\(|\.to\.map\(" -C3 || true

echo
echo "== direct usages of message.headers.(to|cc|from|reply-to) =="
rg -n --hidden -S "message\.headers\.(to|cc|from|reply-to)" -C3 || true

echo
echo "== formatFromEmailWithName / formatToEmailWithName uses/definitions =="
rg -n --hidden -S "formatFromEmailWithName|formatToEmailWithName" -C3 || true

echo
echo "== helpers that might parse/strip display names =="
rg -n --hidden -S "extractEmail|parseAddress|onlyEmail|stripDisplayName|addressOnly|emailOnly|parseMailbox" -C3 || true

echo
echo "== headers.*< pattern (display-name with angle brackets) =="
rg -n --hidden -S "headers\.(to|cc|from|reply-to).*<" -C3 || true

Length of output: 102752


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== apps/web/utils/email/reply-all.ts =="
if [ -f apps/web/utils/email/reply-all.ts ]; then
  sed -n '1,240p' apps/web/utils/email/reply-all.ts || true
else
  echo "MISSING: apps/web/utils/email/reply-all.ts"
fi

echo
echo "== apps/web/utils/email.ts (extract helpers) =="
if [ -f apps/web/utils/email.ts ]; then
  sed -n '1,240p' apps/web/utils/email.ts || true
else
  echo "MISSING: apps/web/utils/email.ts"
fi

echo
echo "== apps/web/utils/email/reply-all.test.ts =="
if [ -f apps/web/utils/email/reply-all.test.ts ]; then
  sed -n '1,240p' apps/web/utils/email/reply-all.test.ts || true
else
  echo "MISSING: apps/web/utils/email/reply-all.test.ts"
fi

Length of output: 10989


Sanitize reply-all recipients to bare emails before calling Outlook Graph

buildReplyAllRecipients can return display‑name formats (e.g. "Alice Smith alice@example.com"); Outlook Graph requires bare addresses. Import extractEmailAddress from "@/utils/email" and use extractEmailAddress(addr) || addr.trim() when building toRecipients, ccRecipients, bccRecipients and replyTo in apps/web/utils/outlook/mail.ts (reply flow ~lines 76–96 and draft flow ~lines 186–205).

🤖 Prompt for AI Agents
In apps/web/utils/outlook/mail.ts around lines 76–83 (reply flow, extend to
~76–96) and around lines 186–205 (draft flow), buildReplyAllRecipients can
return display-name formats; Outlook Graph requires bare email addresses. Import
extractEmailAddress from "@/utils/email" and when mapping recipients
(toRecipients, ccRecipients, bccRecipients, replyTo) call
extractEmailAddress(addr) and fall back to addr.trim() if it returns falsy,
using that value for the emailAddress.address fields (or replyTo entries) so all
addresses passed to Graph are sanitized bare emails.

Comment on lines 90 to 98
toRecipients: [
{
emailAddress: {
address: message.headers["reply-to"] || message.headers.from,
address: recipients.to,
},
},
],
...(ccRecipients.length > 0 ? { ccRecipients } : {}),
conversationId: message.threadId,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Subject/threading OK; normalize recipients.to for Outlook

Same normalization concern for toRecipients. If upstream may include display names, sanitize before assigning to .address.

-          address: recipients.to,
+          address: recipients.to.trim(),

🌐 Web query:

Does Microsoft Graph's Message.toRecipients.emailAddress.address accept "Name <email@domain>" or only the raw email address?

💡 Result:

Short answer: Only the raw email address. Use the separate name property for the display name.

Evidence:

  • The emailAddress resource documents "address" as "The email address of the person or entity" (string) and has a separate "name" property for the display name. [1]
  • Microsoft sendMail/message examples set "emailAddress.address" to an address like "frannis@contoso.com" and put a display name in "emailAddress.name" when needed. [2]

References:
[1] Microsoft Graph — emailAddress resource (address/name).
[2] Microsoft Graph — sendMail / message JSON examples.


Subject/threading OK; split recipients.to into address + name for Outlook

Microsoft Graph requires emailAddress.address to be a raw email; put any display name in emailAddress.name. Parse inputs like "Name email@domain" (or use an RFC‑5322/address parser), set emailAddress.address = parsedEmail and emailAddress.name = parsedName; otherwise fallback to recipients.to.trim().

File: apps/web/utils/outlook/mail.ts — lines 90–98

🤖 Prompt for AI Agents
In apps/web/utils/outlook/mail.ts around lines 90 to 98, the code currently sets
emailAddress.address to recipients.to which may include a display name; instead
parse recipients.to into a raw email and optional display name (e.g., support
formats like "Name <email@domain>" or use an RFC‑5322/address parser library),
then set emailAddress.address = parsedEmail and emailAddress.name = parsedName
(if present); if parsing fails fallback to recipients.to.trim() as the address
and leave name undefined. Ensure ccRecipients and conversationId handling remain
unchanged.

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 4 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="apps/web/utils/email/reply-all.ts">

<violation number="1" location="apps/web/utils/email/reply-all.ts:29">
Splitting address list on a plain comma can break for quoted display names containing commas, producing incorrect CC recipients.</violation>

<violation number="2" location="apps/web/utils/email/reply-all.ts:31">
Excluding the primary recipient via raw string comparison is unreliable. Parse and normalize addresses before comparison so display‑name and case variants are correctly excluded and deduplicated in the CC list.</violation>
</file>

<file name="apps/web/utils/outlook/mail.ts">

<violation number="1" location="apps/web/utils/outlook/mail.ts:81">
Outlook Graph requires emailAddress.address to be a bare email address; passing display‑name formatted values (e.g., &quot;Name &lt;a@b.com&gt;&quot;) can cause the request to fail. Sanitize CC entries by extracting the raw email before constructing ccRecipients.</violation>

<violation number="2" location="apps/web/utils/outlook/mail.ts:93">
Normalize the To recipient for Outlook by parsing into raw email and optional display name. Set emailAddress.address to the parsed email and, if present, emailAddress.name to the display name to avoid Graph validation errors.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

// Add original CC recipients if they exist
if (headers.cc) {
const originalCcAddresses = headers.cc
.split(",")
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 14, 2025

Choose a reason for hiding this comment

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

Splitting address list on a plain comma can break for quoted display names containing commas, producing incorrect CC recipients.

Prompt for AI agents
Address the following comment on apps/web/utils/email/reply-all.ts at line 29:

<comment>Splitting address list on a plain comma can break for quoted display names containing commas, producing incorrect CC recipients.</comment>

<file context>
@@ -0,0 +1,58 @@
+  // Add original CC recipients if they exist
+  if (headers.cc) {
+    const originalCcAddresses = headers.cc
+      .split(&quot;,&quot;)
+      .map((addr) =&gt; addr.trim())
+      .filter((addr) =&gt; addr &amp;&amp; addr !== replyTo);
</file context>
Fix with Cubic

{
emailAddress: {
address: message.headers["reply-to"] || message.headers.from,
address: recipients.to,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 14, 2025

Choose a reason for hiding this comment

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

Normalize the To recipient for Outlook by parsing into raw email and optional display name. Set emailAddress.address to the parsed email and, if present, emailAddress.name to the display name to avoid Graph validation errors.

Prompt for AI agents
Address the following comment on apps/web/utils/outlook/mail.ts at line 93:

<comment>Normalize the To recipient for Outlook by parsing into raw email and optional display name. Set emailAddress.address to the parsed email and, if present, emailAddress.name to the display name to avoid Graph validation errors.</comment>

<file context>
@@ -81,10 +90,11 @@ export async function replyToEmail(
       {
         emailAddress: {
-          address: message.headers[&quot;reply-to&quot;] || message.headers.from,
+          address: recipients.to,
         },
       },
</file context>
Fix with Cubic


// Convert CC addresses to Outlook format
const ccRecipients = recipients.cc.map((addr) => ({
emailAddress: { address: addr },
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 14, 2025

Choose a reason for hiding this comment

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

Outlook Graph requires emailAddress.address to be a bare email address; passing display‑name formatted values (e.g., "Name a@b.com") can cause the request to fail. Sanitize CC entries by extracting the raw email before constructing ccRecipients.

Prompt for AI agents
Address the following comment on apps/web/utils/outlook/mail.ts at line 81:

<comment>Outlook Graph requires emailAddress.address to be a bare email address; passing display‑name formatted values (e.g., &quot;Name &lt;a@b.com&gt;&quot;) can cause the request to fail. Sanitize CC entries by extracting the raw email before constructing ccRecipients.</comment>

<file context>
@@ -72,6 +73,14 @@ export async function replyToEmail(
+
+  // Convert CC addresses to Outlook format
+  const ccRecipients = recipients.cc.map((addr) =&gt; ({
+    emailAddress: { address: addr },
+  }));
+
</file context>

✅ Addressed in fa64e6a

const originalCcAddresses = headers.cc
.split(",")
.map((addr) => addr.trim())
.filter((addr) => addr && addr !== replyTo);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 14, 2025

Choose a reason for hiding this comment

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

Excluding the primary recipient via raw string comparison is unreliable. Parse and normalize addresses before comparison so display‑name and case variants are correctly excluded and deduplicated in the CC list.

Prompt for AI agents
Address the following comment on apps/web/utils/email/reply-all.ts at line 31:

<comment>Excluding the primary recipient via raw string comparison is unreliable. Parse and normalize addresses before comparison so display‑name and case variants are correctly excluded and deduplicated in the CC list.</comment>

<file context>
@@ -0,0 +1,58 @@
+    const originalCcAddresses = headers.cc
+      .split(&quot;,&quot;)
+      .map((addr) =&gt; addr.trim())
+      .filter((addr) =&gt; addr &amp;&amp; addr !== replyTo);
+
+    originalCcAddresses.forEach((addr) =&gt; ccSet.add(addr));
</file context>

✅ Addressed in a01b857

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 (1)
apps/web/utils/gmail/mail.ts (1)

255-258: Reply‑all default for drafts LGTM; no BCC carry‑over

Using buildReplyAllRecipients + formatCcList is correct, and dropping BCC by default matches prior feedback to avoid surprising users.

🧹 Nitpick comments (2)
apps/web/utils/gmail/mail.ts (2)

149-149: Clarify intent: single‑recipient reply here

Avoid confusion now that drafts use reply‑all by default; make the comment explicit.

-  // Only replying to the original sender
+  // Intentionally reply to sender only; draftEmail() applies reply‑all behavior

253-258: Optional: exclude “me” from CC for Gmail‑parity

Most clients omit the signed‑in account from CC on reply‑all. Consider filtering it out; keep current behavior if the profile call fails.

   const recipients = buildReplyAllRecipients(originalEmail.headers, args.to);
+  // Exclude the signed‑in account from CC (Gmail UI behavior)
+  let ccFiltered = recipients.cc;
+  try {
+    const { data: profile } = await gmail.users.getProfile({ userId: "me" });
+    const myEmail = profile.emailAddress?.toLowerCase();
+    if (myEmail) {
+      ccFiltered = recipients.cc.filter(
+        (addr) => addr.toLowerCase() !== myEmail,
+      );
+    }
+  } catch {
+    // best-effort; proceed without filtering if profile lookup fails
+  }

   const raw = await createRawMailMessage({
     to: recipients.to,
-    cc: formatCcList(recipients.cc),
+    cc: formatCcList(ccFiltered),
     subject: args.subject || originalEmail.headers.subject,
📜 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 aa05ead and fa64e6a.

📒 Files selected for processing (2)
  • apps/web/utils/gmail/mail.ts (3 hunks)
  • apps/web/utils/outlook/mail.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/utils/outlook/mail.ts
🧰 Additional context used
📓 Path-based instructions (9)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/gmail/mail.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/gmail/mail.ts
**/*.ts

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

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/gmail/mail.ts
apps/web/utils/gmail/**/*.ts

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

Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Files:

  • apps/web/utils/gmail/mail.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/gmail/mail.ts
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/gmail/mail.ts
apps/web/utils/**/*.ts

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

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/gmail/mail.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/gmail/mail.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/gmail/mail.ts
🧬 Code graph analysis (1)
apps/web/utils/gmail/mail.ts (1)
apps/web/utils/email/reply-all.ts (2)
  • buildReplyAllRecipients (16-50)
  • formatCcList (56-58)
🔇 Additional comments (1)
apps/web/utils/gmail/mail.ts (1)

20-20: Good: shared reply‑all helpers via @/ alias

Clean import of centralized helpers; aligns with path‑alias and reuse guidelines.

@elie222 elie222 merged commit 94c1065 into main Sep 14, 2025
6 of 9 checks passed
@elie222 elie222 deleted the cursor/default-to-reply-all-recipients-2d23 branch September 14, 2025 13:02
This was referenced Sep 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants