Skip to content

feat(platform): add inline image (CID) support for email previews#478

Merged
larryro merged 4 commits into
mainfrom
feat/email-inline-images
Feb 16, 2026
Merged

feat(platform): add inline image (CID) support for email previews#478
larryro merged 4 commits into
mainfrom
feat/email-inline-images

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Feb 16, 2026

Summary

  • Add support for inline images in email previews by resolving cid: references to attachment URLs
  • Propagate contentId through the full pipeline: connectors (Gmail/Outlook) → schema → transform → UI
  • Auto-download inline image attachments when they lack URLs, and hide them from the attachment list since they render inline

Changes

  • Connectors: Extract Content-ID headers from Gmail and Outlook attachment metadata
  • Schema/types: Add optional contentId field to attachment schemas (EmailType, emailAttachmentMetaSchema)
  • Backend: Correlate connector return data with file references by index to populate contentId on download; surface contentId in transform
  • UI: EmailPreview resolves cid: src attributes via a CID→URL map; Message component auto-triggers download for unresolved inline images and filters inline attachments from the displayed list
  • Outlook workflow config: Expand full attachment data (remove $select filter) so contentId is available
  • Sandbox: Increase MAX_PASSES from 10 to 50 to handle emails with many inline images

Test plan

  • Unit tests for replaceCidReferences and EmailPreview component with CID map
  • Verify Gmail emails with inline images render correctly
  • Verify Outlook emails with inline images render correctly
  • Confirm backwards compatibility with existing emails (no contentId)
  • Confirm regular (non-inline) attachments still display normally

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Emails now properly display inline images directly in message content
    • Inline images are automatically downloaded when viewing messages
  • Improvements

    • Enhanced attachment handling for inline images in Gmail and Outlook integrations

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Feb 16, 2026

Greptile Summary

Implemented end-to-end support for inline images in email previews by propagating contentId from email providers through the backend to the UI, where cid: references are resolved to attachment URLs.

Key changes:

  • Connectors extract Content-ID headers from Gmail/Outlook attachments and strip angle brackets
  • Schema and type definitions updated to include optional contentId field
  • Backend correlates connector data with file references by array index to populate contentId during download
  • UI builds CID→URL map, replaces cid: references before sanitization, and filters inline attachments from display list
  • Auto-download mechanism triggers for unresolved inline images
  • Outlook workflow config expanded to fetch full attachment metadata
  • Sandbox MAX_PASSES increased from 10 to 50 for emails with many inline images

Issues found:

  • Auto-download logic for legacy syncs may trigger unnecessarily if email body contains "cid:" text but attachments are regular (non-inline)

Confidence Score: 4/5

  • Safe to merge with one minor logic refinement recommended
  • Implementation is well-structured with good test coverage for core functionality. Index-based correlation logic handles duplicate filenames correctly. One edge case in auto-download logic could trigger unnecessary downloads for legacy emails with regular attachments and "cid:" in body text.
  • Pay attention to services/platform/app/features/conversations/components/message.tsx (line 224-227) for the auto-download edge case

Important Files Changed

Filename Overview
examples/integrations/gmail/connector.js Extracts Content-ID header from Gmail attachments and strips angle brackets
examples/integrations/outlook/connector.js Extracts contentId from Outlook attachment metadata and strips angle brackets
services/platform/convex/conversations/internal_actions.ts Correlates connector data with file refs by index to populate contentId on download, handles duplicate filenames
services/platform/app/components/ui/data-display/email-preview.tsx Adds replaceCidReferences function and applies CID→URL mapping before sanitization
services/platform/app/features/conversations/components/message.tsx Builds CID map, filters inline attachments from display, auto-triggers download for unresolved inline images

Flowchart

flowchart TD
    A[Email Sync] --> B[Connector extracts Content-ID]
    B --> C{Provider?}
    C -->|Gmail| D[Parse Content-ID header<br/>Strip angle brackets]
    C -->|Outlook| E[Read contentId field<br/>Strip angle brackets]
    D --> F[Return EmailType with<br/>attachment.contentId]
    E --> F
    F --> G[Store in message metadata]
    G --> H{Attachment downloaded?}
    H -->|No| I[UI detects unresolved<br/>inline image]
    I --> J[Auto-trigger download]
    J --> K[downloadAttachmentsAction]
    H -->|Yes| L[Build CID→URL map]
    K --> M[Correlate by array index]
    M --> N[Populate contentId from<br/>connector return data]
    N --> O[Update attachment with<br/>storageId + url + contentId]
    O --> L
    L --> P[replaceCidReferences]
    P --> Q[Replace src='cid:X' with<br/>src='URL']
    Q --> R[Sanitize HTML]
    R --> S[Render EmailPreview]
    L --> T[Filter displayAttachments]
    T --> U[Hide inline images<br/>from attachment list]
Loading

Last reviewed commit: 4814a25

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

12 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread services/platform/app/features/conversations/components/message.tsx Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

This PR adds end-to-end support for propagating Content-ID (CID) metadata from email attachments across Gmail and Outlook connectors, frontend UI rendering, and backend services. The changes extract contentId from email headers in connectors, simplify Outlook API queries to fetch all attachment fields, enhance the EmailPreview component to replace cid: references with actual URLs via a cidMap prop, update the message component to build cidMap from attachments and auto-download inline images, extend Convex schemas and conversation actions to store and correlate contentId throughout the pipeline, and increase MAX_PASSES from 10 to 50.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (101 files):

⚔️ .github/workflows/build.yml (content)
⚔️ commitlint.config.mjs (content)
⚔️ examples/integrations/gmail/connector.js (content)
⚔️ examples/integrations/outlook/connector.js (content)
⚔️ examples/workflows/outlook-email-sync/config.json (content)
⚔️ package-lock.json (content)
⚔️ services/crawler/Dockerfile (content)
⚔️ services/operator/Dockerfile (content)
⚔️ services/platform/.storybook/main.js (content)
⚔️ services/platform/.storybook/manager.ts (content)
⚔️ services/platform/.storybook/preview.tsx (content)
⚔️ services/platform/.storybook/theme.ts (content)
⚔️ services/platform/Dockerfile (content)
⚔️ services/platform/app/components/theme/theme-provider.tsx (content)
⚔️ services/platform/app/components/ui/data-display/email-preview.tsx (content)
⚔️ services/platform/app/components/ui/data-table/data-table-action-menu.stories.tsx (content)
⚔️ services/platform/app/components/ui/data-table/data-table-action-menu.tsx (content)
⚔️ services/platform/app/components/ui/data-table/data-table.stories.tsx (content)
⚔️ services/platform/app/components/ui/dialog/confirm-dialog.tsx (content)
⚔️ services/platform/app/components/ui/dialog/delete-dialog.stories.tsx (content)
⚔️ services/platform/app/components/ui/dialog/dialog.tsx (content)
⚔️ services/platform/app/components/ui/dialog/form-dialog.stories.tsx (content)
⚔️ services/platform/app/components/ui/dialog/form-dialog.tsx (content)
⚔️ services/platform/app/components/ui/dialog/view-dialog.stories.tsx (content)
⚔️ services/platform/app/components/ui/filters/filter-button.tsx (content)
⚔️ services/platform/app/components/ui/filters/filters.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/checkbox.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/date-range-picker.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/date-range-picker.tsx (content)
⚔️ services/platform/app/components/ui/forms/input.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/json-input.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/radio-group.tsx (content)
⚔️ services/platform/app/components/ui/forms/search-input.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/select.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/switch.stories.tsx (content)
⚔️ services/platform/app/components/ui/forms/switch.tsx (content)
⚔️ services/platform/app/components/ui/forms/textarea.stories.tsx (content)
⚔️ services/platform/app/components/ui/layout/card.stories.tsx (content)
⚔️ services/platform/app/components/ui/layout/layout.stories.tsx (content)
⚔️ services/platform/app/components/ui/logo/logo.stories.tsx (content)
⚔️ services/platform/app/components/ui/navigation/navigation.stories.tsx (content)
⚔️ services/platform/app/components/ui/navigation/pagination.stories.tsx (content)
⚔️ services/platform/app/components/ui/navigation/pagination.tsx (content)
⚔️ services/platform/app/components/ui/navigation/tab-navigation.stories.tsx (content)
⚔️ services/platform/app/components/ui/navigation/tab-navigation.tsx (content)
⚔️ services/platform/app/components/ui/navigation/tabs.stories.tsx (content)
⚔️ services/platform/app/components/ui/navigation/tabs.tsx (content)
⚔️ services/platform/app/components/ui/overlays/confirm-dialog.stories.tsx (content)
⚔️ services/platform/app/components/ui/overlays/dialog.stories.tsx (content)
⚔️ services/platform/app/components/ui/overlays/dropdown-menu.stories.tsx (content)
⚔️ services/platform/app/components/ui/overlays/popover.stories.tsx (content)
⚔️ services/platform/app/components/ui/overlays/sheet.stories.tsx (content)
⚔️ services/platform/app/components/ui/overlays/sheet.tsx (content)
⚔️ services/platform/app/components/ui/overlays/tooltip.stories.tsx (content)
⚔️ services/platform/app/components/ui/primitives/button.stories.tsx (content)
⚔️ services/platform/app/components/ui/primitives/button.test.tsx (content)
⚔️ services/platform/app/components/ui/primitives/button.tsx (content)
⚔️ services/platform/app/components/ui/primitives/icon-button.stories.tsx (content)
⚔️ services/platform/app/components/ui/primitives/icon-button.test.tsx (content)
⚔️ services/platform/app/features/approvals/components/approval-detail-dialog.tsx (content)
⚔️ services/platform/app/features/approvals/components/approvals-client.tsx (content)
⚔️ services/platform/app/features/automations/components/automation-navigation.tsx (content)
⚔️ services/platform/app/features/automations/components/automation-steps.tsx (content)
⚔️ services/platform/app/features/automations/components/automation-tester.tsx (content)
⚔️ services/platform/app/features/automations/triggers/components/events-section.tsx (content)
⚔️ services/platform/app/features/automations/triggers/components/schedule-create-dialog.tsx (content)
⚔️ services/platform/app/features/automations/triggers/components/schedules-section.tsx (content)
⚔️ services/platform/app/features/automations/triggers/components/secret-reveal-dialog.tsx (content)
⚔️ services/platform/app/features/automations/triggers/components/webhooks-section.tsx (content)
⚔️ services/platform/app/features/conversations/components/conversations-client.tsx (content)
⚔️ services/platform/app/features/conversations/components/message.tsx (content)
⚔️ services/platform/app/features/custom-agents/components/custom-agent-navigation.tsx (content)
⚔️ services/platform/app/features/custom-agents/components/custom-agent-version-history-dialog.tsx (content)
⚔️ services/platform/app/features/custom-agents/components/custom-agent-webhook-section.tsx (content)
⚔️ services/platform/app/features/customers/components/customer-import-form.tsx (content)
⚔️ services/platform/app/features/documents/components/document-team-tags-dialog.tsx (content)
⚔️ services/platform/app/features/documents/components/microsoft-reauth-button.tsx (content)
⚔️ services/platform/app/features/documents/components/onedrive-import-dialog.tsx (content)
⚔️ services/platform/app/features/products/components/product-import-form.tsx (content)
⚔️ services/platform/app/features/products/components/product-view-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/components/circuly-integration-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/components/integration-manage-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/components/integration-upload/integration-upload-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/components/shopify-integration-dialog.tsx (content)
⚔️ services/platform/app/features/settings/integrations/components/sso-config-dialog.tsx (content)
⚔️ services/platform/app/features/settings/teams/components/team-members-dialog.tsx (content)
⚔️ services/platform/app/features/tone-of-voice/components/example-view-edit-dialog.tsx (content)
⚔️ services/platform/app/features/tone-of-voice/components/tone-of-voice-form-client.tsx (content)
⚔️ services/platform/app/features/vendors/components/vendor-import-form.tsx (content)
⚔️ services/platform/app/features/websites/components/website-view-dialog.tsx (content)
⚔️ services/platform/app/routes/_auth/log-in.tsx (content)
⚔️ services/platform/app/routes/_auth/sign-up.tsx (content)
⚔️ services/platform/convex/conversations/add_message_to_conversation.ts (content)
⚔️ services/platform/convex/conversations/internal_actions.ts (content)
⚔️ services/platform/convex/conversations/transform_conversation.ts (content)
⚔️ services/platform/convex/node_only/integration_sandbox/helpers/run_with_passes.ts (content)
⚔️ services/platform/convex/workflow_engine/action_defs/conversation/helpers/types.ts (content)
⚔️ services/platform/lib/shared/schemas/conversations.ts (content)
⚔️ services/platform/package.json (content)
⚔️ services/rag/Dockerfile (content)
⚔️ turbo.json (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main feature: adding inline image (CID) support for email previews, which is the primary objective across all changed files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/email-inline-images
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/email-inline-images
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

Copy link
Copy Markdown

@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: 4

🤖 Fix all issues with AI agents
In `@examples/workflows/outlook-email-sync/config.json`:
- Around line 67-71: The config uses "expand": "attachments" without an explicit
nested $select which relies on Graph's defaults; update the request parameters
(the JSON entries with "select" and "expand": "attachments") to include an
attachments $select specifying the exact attachment properties you need (for
example id, name, contentType, size, isInline, lastModifiedDateTime and only
include contentBytes if you actually need payloads) so intent is explicit and
future-proof—apply the same change to the other similar blocks referenced (the
other "select"/"expand" occurrences).

In `@services/platform/app/features/conversations/components/message.tsx`:
- Around line 195-204: Normalize attachment Content‑ID values before inserting
into cidMap so CID keys match inline `cid:` references; inside the useMemo that
builds cidMap (the const cidMap = useMemo(...) block iterating
message.attachments), trim surrounding whitespace and remove leading/trailing
angle brackets from att.contentId (and optionally normalize case) and use that
cleaned value as the map key (still storing att.url as the value) so lookups for
`cid:...` in HTML will match.
- Around line 206-232: displayAttachments currently removes all inline
attachments (att.contentId), which hides unresolved items and prevents manual
retry; change the filter in the displayAttachments useMemo to only exclude
inline attachments that are resolved (i.e., have both contentId and a url) so
unresolved inline attachments remain visible for retry. Update the useMemo that
defines displayAttachments to something like filtering out attachments where
att.contentId && att.url (reference: displayAttachments, message.attachments),
and leave the auto-download logic using inlineDownloadTriggered and
onDownloadAttachments unchanged so users can still trigger downloads but can
also manually retry visible unresolved attachments.

In
`@services/platform/convex/node_only/integration_sandbox/helpers/run_with_passes.ts`:
- Line 23: Add rationale for the 50-pass limit or make it configurable: either
add a short inline comment next to MAX_PASSES explaining why 50 was chosen
(e.g., to support many inline images while relying on existing timeoutMs
safety), or introduce a DEFAULT_MAX_PASSES constant and add maxPasses to
RunWithPassesParams and to the runWithPasses parameter destructure (e.g., const
{ ..., maxPasses = DEFAULT_MAX_PASSES } = params) so callers can override the
limit; update usages of MAX_PASSES in runWithPasses to use the new maxPasses
variable.

Comment thread examples/workflows/outlook-email-sync/config.json
Comment thread services/platform/app/features/conversations/components/message.tsx
Comment thread services/platform/app/features/conversations/components/message.tsx Outdated
Resolve cid: references in email HTML to actual attachment URLs so
inline images render correctly. Connectors now extract Content-ID
headers, the backend correlates them during attachment download, and
the EmailPreview component replaces cid: src attributes with resolved
URLs. Auto-triggers attachment download when unresolved inline images
are detected.
@larryro larryro force-pushed the feat/email-inline-images branch from 2c70fab to d88661d Compare February 16, 2026 14:33
@larryro larryro merged commit 84aa3fd into main Feb 16, 2026
17 checks passed
@larryro larryro deleted the feat/email-inline-images branch February 16, 2026 14:58
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.

1 participant