feat(email): incoming mail (IMAP) intake - backend + standalone flag

Adds a second mail config (incoming/IMAP) alongside the outgoing SMTP one, a
1-minute poller, and a received-emails log. Standalone `incomingMail` feature
flag (default off).

- deps: imapflow + mailparser (receive-side; picpeak only had nodemailer).
- migration 128: email_configs gains imap_* columns (same shape as smtp_*);
  seed incomingMail flag; new received_emails audit table.
- emailIntakeService: polls the mailbox every 60s when the flag is on AND a
  mailbox is configured (no-op otherwise); parses each unseen message
  (mailparser flattens forwarded/nested attachments), drops PDF/JPEG/PNG into
  the incoming-invoices inbox (inbound_documents, source='email'), logs each
  message in received_emails (dedupe by message-id; duplicate attachments
  caught by the existing SHA-256 guard), marks it \Seen.
- adminEmail: GET/POST /incoming-config (mirrors SMTP config, masks imap_pass,
  SSRF host guard) + GET /received (paginated log).
- server.js starts the poller at boot.

Verified: node -c, require-graph, migration-128 harness (imap columns, flag,
received_emails). Frontend (IMAP block under SMTP + Received tab + flag card)
follows.
This commit is contained in:
Luca
2026-06-11 15:54:43 +02:00
parent 81af4453e7
commit 5645c304ab
7 changed files with 666 additions and 13 deletions
+2
View File
@@ -35,11 +35,13 @@
"i18next": "25.3.2",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"imapflow": "^1.4.0",
"ipaddr.js": "^2.3.0",
"joi": "^17.9.1",
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.0",
"knex": "^2.4.2",
"mailparser": "^3.9.9",
"mime-types": "^3.0.1",
"multer": "^2.0.2",
"node-cron": "^3.0.2",