chatwoot/app/helpers
chinatsu1124 4371793741
fix: captain-v2 cannot see image attachments shared via email (#14449)
## Description

Inbound email attachments are stored with `file_type: 'file'` regardless
of their actual MIME type. As a result, image screenshots shared by
customers via email are not exposed to Captain V2's multimodal pipeline
— `Captain::OpenAiMessageBuilderService#attachment_parts` selects images
via `attachments.where(file_type: :image)` and emits a placeholder
`"User has shared an attachment"` text part instead of an `image_url`
part. The model never gets the image, so Captain keeps asking the
customer to retype information that is already visible in the
screenshot.

This PR makes the email mailbox derive `file_type` from the blob's
`content_type` using the existing shared `FileTypeHelper`, matching how
every other inbound channel (`twilio`, `sms`, `telegram`, `line`,
`tiktok`, `twitter`, `messenger`) and `MessageBuilder` already classify
attachments.

Fixes #14448

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Reproduced and verified on a self-hosted production instance:

1. Real customer reply via email with a PNG screenshot of an in-app
error.

   Before:
   ```ruby
   a = Message.find(<id>).attachments.first
   a.file_type                # => "file"
   a.file.blob.content_type   # => "image/png"
Captain::OpenAiMessageBuilderService.new(message:
a.message).generate_content
   # => [{type: 'text', text: '...'},
# {type: 'text', text: 'User has shared an attachment'}]  no image_url
   ```
Captain reply: "Please copy and paste the full error text…" (model never
saw the image).

2. After the patch + force-recreate, same conversation:
   ```ruby
   a.file_type                # => "image"
Captain::OpenAiMessageBuilderService.new(message:
a.message).generate_content
   # => [{type: 'text',      text: '...'},
# {type: 'image_url', image_url: {url: 'https://.../<blob>.png'}}] 
   ```
Captain reply now correctly references the on-screen error text from the
screenshot via the multimodal vision path — no more deflection.

3. Regression sanity-check on non-image attachments (PDF / Office docs):
`file_type` falls through to `:file`, behavior unchanged.

## Notes for self-hosted operators

Existing email image attachments in the DB will still have `file_type:
'file'`. A one-shot backfill is straightforward and safe (no data loss,
only metadata):

```ruby
Attachment.joins(message: :conversation)
          .where(messages: { content_type: 'incoming_email' })
          .where(file_type: 'file')
          .find_each do |a|
  next unless a.file.attached?
  ct = a.file.blob.content_type.to_s
  next unless ct.start_with?('image/', 'audio/', 'video/')
  new_type = ct.start_with?('image/') ? :image : (ct.start_with?('video/') ? :video : :audio)
  a.update_columns(file_type: Attachment.file_types[new_type])
end
```

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective — happy to add a
`mailbox_helper_spec` example for `process_regular_attachments` if
maintainers prefer; existing specs in that file focus on inline-image
handling.

---------

Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
2026-05-19 10:11:32 +05:30
..
api feat: Ability to specify the authentication type for imap server (#12306) 2026-05-08 16:40:15 +05:30
filters feat: harden filter service 2026-03-09 21:19:20 +05:30
instagram feat: Added the ability to create Instagram channel (#11182) 2025-04-03 13:57:14 +05:30
linear feat: move Linear config to installation_config (#10999) 2025-02-28 14:20:27 +05:30
shopify feat(apps): Shopify Integration (#11101) 2025-03-19 15:37:55 -07:00
super_admin feat: new Captain Editor (#13235) 2026-01-21 13:39:07 +05:30
tiktok feat: TikTok channel (#12741) 2025-12-17 07:54:50 -08:00
application_helper.rb feat: Add help URLs for features in features.yml (#9134) 2024-03-21 10:30:46 -07:00
billing_helper.rb feat: Upgrade page instead of banner (#11202) 2025-03-28 02:28:17 -07:00
cache_keys_helper.rb feat: IndexedDB based caching for labels, inboxes and teams [CW-50] (#6710) 2023-03-27 12:16:25 +05:30
contact_helper.rb feat: Add contact helper (#8989) 2024-03-06 17:39:39 +05:30
data_helper.rb fix: Issue with processing variables in outgoing email content (#12799) 2025-11-10 20:50:02 +05:30
date_range_helper.rb fix: inconsistency in report and summary for metric counts (#6817) 2023-04-20 12:55:04 +05:30
email_helper.rb fix: Preserve single newlines in outgoing email messages (#14138) 2026-04-28 12:47:03 +04:00
file_type_helper.rb fix: captain-v2 cannot see image attachments shared via email (#14449) 2026-05-19 10:11:32 +05:30
frontend_urls_helper.rb 🚨Fix Rubocop lint errors 2019-10-20 14:17:26 +05:30
home_helper.rb Initial Commit 2019-08-14 15:18:44 +05:30
message_format_helper.rb fix: Handle emoji and special characters in mention notifications (#11857) 2025-07-03 11:32:13 +05:30
portal_helper.rb feat: Add a documentation layout design for public help center portal (#14403) 2026-05-18 12:30:08 -07:00
report_helper.rb fix: prevent bot metrics double-counting when handoff and resolution coexist [CW-6210] (#14032) 2026-05-13 18:43:23 +05:30
reporting_event_helper.rb fix: incorrect first response time for reopened conversations (#12058) 2025-08-13 16:39:43 +05:30
timezone_helper.rb feat(rollup): add models and write path [1/3] (#13796) 2026-03-19 13:12:36 +05:30
widget_helper.rb chore: Enhance contact merge action for identified users (#4886) 2022-06-23 15:48:56 +05:30