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:
@@ -110,6 +110,15 @@ class ImapAccountCreate(BaseModel):
|
||||
use_ssl: bool = Field(default=True, description="Use SSL/TLS connection")
|
||||
delete_after_process: bool = Field(default=False, description="Delete emails from mailbox after processing")
|
||||
is_active: bool = Field(default=True, description="Whether to poll this mailbox")
|
||||
attachment_filter: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Controls which attachment types to ingest. "
|
||||
"'documents_only' – PDFs and office files only (default when None). "
|
||||
"'all' – all supported types including images. "
|
||||
"Null inherits the global imap_attachment_filter setting."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ImapAccountUpdate(BaseModel):
|
||||
@@ -123,6 +132,15 @@ class ImapAccountUpdate(BaseModel):
|
||||
use_ssl: bool | None = None
|
||||
delete_after_process: bool | None = None
|
||||
is_active: bool | None = None
|
||||
attachment_filter: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Controls which attachment types to ingest. "
|
||||
"'documents_only' – PDFs and office files only. "
|
||||
"'all' – all supported types including images. "
|
||||
"Null or empty string clears the override (inherits global setting)."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ImapTestRequest(BaseModel):
|
||||
@@ -155,6 +173,7 @@ def _to_response(acct: UserImapAccount) -> dict[str, Any]:
|
||||
"use_ssl": acct.use_ssl,
|
||||
"delete_after_process": acct.delete_after_process,
|
||||
"is_active": acct.is_active,
|
||||
"attachment_filter": acct.attachment_filter,
|
||||
"last_checked_at": acct.last_checked_at.isoformat() if acct.last_checked_at else None,
|
||||
"last_error": acct.last_error,
|
||||
"created_at": acct.created_at.isoformat() if acct.created_at else None,
|
||||
@@ -222,6 +241,7 @@ def create_imap_account(
|
||||
use_ssl=body.use_ssl,
|
||||
delete_after_process=body.delete_after_process,
|
||||
is_active=body.is_active,
|
||||
attachment_filter=body.attachment_filter or None,
|
||||
)
|
||||
try:
|
||||
db.add(acct)
|
||||
@@ -277,6 +297,12 @@ def update_imap_account(
|
||||
acct.delete_after_process = body.delete_after_process
|
||||
if body.is_active is not None:
|
||||
acct.is_active = body.is_active
|
||||
# attachment_filter uses a sentinel check: the field is always present in the
|
||||
# model (defaulting to None in Pydantic) so we update it unconditionally when
|
||||
# the caller sends any value (including explicit null to clear the override).
|
||||
# An empty string is normalised to None to avoid storing a non-meaningful value.
|
||||
if "attachment_filter" in body.model_fields_set:
|
||||
acct.attachment_filter = body.attachment_filter or None
|
||||
|
||||
# Reset last_error so the next poll gives a fresh result
|
||||
acct.last_error = None
|
||||
|
||||
Reference in New Issue
Block a user