Skip to content

Add Correspondence side panel section for email and calendar - #5418

Open
jbecke wants to merge 1 commit into
mainfrom
claude/correspondence-bento-section-c53j4z
Open

Add Correspondence side panel section for email and calendar#5418
jbecke wants to merge 1 commit into
mainfrom
claude/correspondence-bento-section-c53j4z

Conversation

@jbecke

@jbecke jbecke commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new "Correspondence" side panel section that displays external parties, their companies, and recent email history for email threads and calendar events. This feature helps users quickly understand who they're communicating with and access related CRM records.

Key Changes

  • New Correspondence Section Component (CorrespondenceSection.tsx): Main UI component that displays:

    • External contacts on the thread/event with links to CRM contact records
    • Companies associated with those contacts with links to CRM company records
    • Recent email threads with those parties, scrollable in a fixed-height container
  • Party Filtering Logic (parties.ts): Utility functions to identify external parties:

    • externalParties(): Filters out the signed-in user and same-domain teammates, dedupes on email address
    • addressDomain(): Extracts and normalizes domain from email addresses
    • partyDomains(): Gets distinct domains from a list of parties
    • Comprehensive test coverage for edge cases (malformed addresses, case-insensitivity, deduplication)
  • CRM Record Resolution (use-crm-records.ts):

    • useCrmCompanyForDomain(): Two-hop lookup (domain → company ID via unified search, then ID → company + contacts)
    • useCrmContactForAddress(): Resolves individual contacts through their company's domain
  • Email Thread Querying (use-correspondence-threads.ts):

    • useCorrespondenceThreadsQuery(): Fetches recent email threads with specified addresses using the soup query API
    • Limits results to 50 threads and sorts by most recent first
    • Scoped to email-only to work regardless of CRM enablement
  • Domain Lookup Query (company-for-domain.ts):

    • useCrmCompanyIdForDomainQuery(): Unified search query scoped to CRM companies only
    • Validates exact domain match (not substring) to avoid false positives
    • Generous stale time (5 minutes) to minimize redundant requests
  • Integration Points:

    • Email side panel: Shows correspondence for all visible parties (To/Cc/From, excluding Bcc)
    • Calendar side panel: Shows correspondence for event organizer and attendees (excluding self)
    • Both use externalParties() to filter consistently

Implementation Details

  • Uses TanStack Query for caching and request deduplication across multiple rows sharing the same domain
  • Unified search filters use NIL_UUID sentinel to disable non-CRM sources, keeping queries scoped appropriately
  • Email filter helper emailFilterForAddresses() extended to support multiple addresses with OR logic
  • Graceful fallbacks: shows bare domain when no company is tracked, shows email when no contact name is available
  • All party filtering is case-insensitive and handles whitespace normalization

https://claude.ai/code/session_0172AYowQFMN2UwQTjhXX328

Adds a "Correspondence" bento at the bottom of the email block's and the
calendar's right-hand side panels. It surfaces, for the external parties on
the open thread / selected event:

  1. their CRM contact records (clickable through to the contact panel),
  2. the CRM company record behind each external domain, and
  3. the 50 most recent email threads shared with them, loaded async and
     scrolled inside a fixed-height box so the bento can't stretch.

The section renders only when the thread/event actually has external parties,
where "external" means an address that is neither the signed-in user's nor on
their email domain. On the calendar that additionally means only while an
event is selected.

CRM lookup goes domain -> company id via the unified-search CRM source (the
CRM service has no domain lookup endpoint), then id -> company + contacts via
the existing company endpoint. Search matches domains by substring, so a hit
is accepted only on an exact domain equality check. Rows fall back to what the
thread/event knows about a party when the team tracks no CRM record for them.

The thread list uses the raw any-direction `ef` address filter rather than the
CRM-scoped `eca` widener, so it stays in the caller's own mailbox scope and
works whether or not CRM is enabled for those correspondents.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added correspondence details to email and calendar side panels.
    • View external contacts, associated companies, and recent shared email threads.
    • CRM contacts and companies are linked when available, with loading states and fallback labels.
    • Recent correspondence is limited and opens in the unified split view.
  • Bug Fixes
    • Improved filtering to exclude the current user, hidden recipients, duplicates, and invalid addresses.

Walkthrough

Added shared correspondence utilities that normalize external parties and domains. Added CRM company and contact lookups, recent email-thread queries, and rendering for contacts, companies, and threads. Added correspondence sections to email and calendar side panels, using filtered participants from messages and events. Added tests for party normalization, filtering, deduplication, invalid addresses, and domain ordering.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the main change and is under 72 characters, but it does not follow conventional commits format. Use a conventional commit prefix, such as "feat: add correspondence side panel for email and calendar".
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the Correspondence side-panel feature, integrations, filtering, CRM lookups, and email querying.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/features/correspondence/use-crm-records.ts (1)

33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unnecessary memo.

addressDomain(email()) is a cheap primitive derivation. useCrmCompanyForDomain only needs an accessor. Use a plain accessor instead.

Proposed change
-  const domain = createMemo(() => addressDomain(email()));
+  const domain = () => addressDomain(email());

As per coding guidelines, “Use createMemo only when referential stability is needed or derivation is expensive.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/features/correspondence/use-crm-records.ts` at line 33, Replace
the createMemo-based domain derivation near useCrmCompanyForDomain with a plain
accessor that returns addressDomain(email()), preserving the accessor interface
expected by useCrmCompanyForDomain.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/features/correspondence/use-crm-records.ts`:
- Line 33: Replace the createMemo-based domain derivation near
useCrmCompanyForDomain with a plain accessor that returns
addressDomain(email()), preserving the accessor interface expected by
useCrmCompanyForDomain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 78f15f1a-e2d1-4479-83ac-ceded0e1a3fb

📥 Commits

Reviewing files that changed from the base of the PR and between 1a4fe27 and 2920e2a.

📒 Files selected for processing (11)
  • apps/web/src/features/block-email/component/sidepanel/EmailSidePanelSections.tsx
  • apps/web/src/features/calendar/CalendarSidePanelSections.tsx
  • apps/web/src/features/companies/Company/emailFilter.ts
  • apps/web/src/features/correspondence/CorrespondenceSection.tsx
  • apps/web/src/features/correspondence/index.ts
  • apps/web/src/features/correspondence/parties.test.ts
  • apps/web/src/features/correspondence/parties.ts
  • apps/web/src/features/correspondence/use-correspondence-threads.ts
  • apps/web/src/features/correspondence/use-crm-records.ts
  • apps/web/src/lib/queries/crm/company-for-domain.ts
  • apps/web/src/lib/queries/crm/keys.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants