feat(imap): add attachment type filter for IMAP ingestion

Add a configurable switch to control which attachment types are ingested
via IMAP. Images are excluded by default; office files and PDFs are ingested.

- Add global `IMAP_ATTACHMENT_FILTER` config setting (default: `documents_only`)
- Add `attachment_filter` column to `UserImapAccount` model for per-user override
- Migration 032 adds the column to `user_imap_accounts` table
- Update `fetch_attachments_and_enqueue()` to respect filter (documents_only/all)
- Update `pull_inbox()`, `_pull_user_imap_accounts()`, and
  `_pull_user_integration_imap()` to pass the resolved filter
- Update IMAP accounts API (schemas, create/update handlers, response serializer)
- Update IMAP accounts UI to show attachment filter dropdown in modal and
  display filter badges on account cards
- Add 6 new tests covering attachment filter behaviour
- Update ConfigurationGuide.md, EmailIngestion.md, and .env.demo

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-12 00:59:54 +00:00
parent 5e45b68cc1
commit 554bb21d32
11 changed files with 300 additions and 22 deletions
+32 -1
View File
@@ -172,6 +172,16 @@
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>Delete after process
</span>
</template>
<template x-if="acct.attachment_filter === 'all'">
<span class="inline-flex items-center px-2 py-0.5 rounded bg-blue-50 text-blue-700">
<i class="fas fa-paperclip mr-1" aria-hidden="true"></i>All attachments
</span>
</template>
<template x-if="acct.attachment_filter === 'documents_only'">
<span class="inline-flex items-center px-2 py-0.5 rounded bg-gray-50 text-gray-600">
<i class="fas fa-file-alt mr-1" aria-hidden="true"></i>Documents only
</span>
</template>
<template x-if="acct.last_checked_at">
<span class="text-gray-400" :title="acct.last_checked_at">
<i class="fas fa-clock mr-1" aria-hidden="true"></i>Last polled: <span x-text="formatDate(acct.last_checked_at)"></span>
@@ -386,6 +396,26 @@
</label>
</div>
<!-- Attachment filter -->
<div>
<label for="acct-attachment-filter" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
Attachment Types to Ingest
</label>
<select
id="acct-attachment-filter"
x-model="form.attachment_filter"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
aria-describedby="acct-attachment-filter-hint"
>
<option value="">Use global default</option>
<option value="documents_only">Documents only (PDF, Office, ODT, RTF, TXT, CSV) — no images</option>
<option value="all">All supported types (including images)</option>
</select>
<p id="acct-attachment-filter-hint" class="mt-1 text-xs text-gray-400 dark:text-gray-500">
Override the system-wide attachment filter for this mailbox. Leave blank to use the global default (documents only).
</p>
</div>
<!-- Test connection result -->
<template x-if="testResult">
<div
@@ -551,7 +581,7 @@ function imapAccountsApp() {
openCreateModal() {
this.editingAccount = null;
this.form = { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true };
this.form = { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true, attachment_filter: '' };
this.showPassword = false;
this.testResult = null;
this.formError = null;
@@ -569,6 +599,7 @@ function imapAccountsApp() {
use_ssl: acct.use_ssl,
delete_after_process: acct.delete_after_process,
is_active: acct.is_active,
attachment_filter: acct.attachment_filter || '',
};
this.showPassword = false;
this.testResult = null;