--- phase: 07-dkv-fleet-module plan: 02 subsystem: dkv-inbox-access-layer tags: [dkv, imap, ews, exchange, imapflow, inbox-provider, dto, class-validator] dependency_graph: requires: [dkv.types.ts (Plan 01), CalendarModule (ews-javascript-api installed)] provides: [InboxProvider interface, ImapProvider, ExchangeInboxProvider, DkvConfigDto, CreateVehicleDto, UpdateVehicleDto, DkvHistoryQueryDto] affects: - apps/api/src/dkv/providers/inbox-provider.interface.ts - apps/api/src/dkv/providers/imap.provider.ts - apps/api/src/dkv/providers/exchange-inbox.provider.ts - apps/api/src/dkv/dto/dkv-config.dto.ts - apps/api/src/dkv/dto/dkv-vehicle.dto.ts - apps/api/src/dkv/dto/dkv-history.dto.ts tech_stack: added: [] patterns: - "imapflow: fetchAll() before any download() — avoids IMAP connection deadlock (Pitfall 1)" - "imapflow: logger:false in ImapFlow constructor — credential safety (T-07-03)" - "imapflow: collectPdfParts() recursive MIME tree traversal for application/pdf parts" - "ews-javascript-api: dynamic import as any, WellKnownFolderName.Inbox + FindItems (not FindAppointments)" - "ews-javascript-api: SearchFilter.ContainsSubstring for server-side sender filtering" - "25MB MAX_ATTACHMENT_BYTES guard in both providers — PDF-bomb mitigation (T-07-05)" - "export type { ... } required for isolatedModules TypeScript setting" - "class-validator: @IsEmail() on senderFilter/exportRecipient, @Min(5) on pollIntervalMin, @IsInt @Min(1) on pagination" key_files: created: - apps/api/src/dkv/providers/inbox-provider.interface.ts - apps/api/src/dkv/providers/imap.provider.ts - apps/api/src/dkv/providers/exchange-inbox.provider.ts - apps/api/src/dkv/dto/dkv-config.dto.ts - apps/api/src/dkv/dto/dkv-vehicle.dto.ts - apps/api/src/dkv/dto/dkv-history.dto.ts modified: [] decisions: - "Max attachment size limit set to 25MB (26_214_400 bytes) — covers all realistic DKV invoices; larger files are skipped with a warning log" - "ExchangeInboxProvider uses server-side SearchFilter.ContainsSubstring for sender filtering plus client-side verification (handles case-insensitivity differences)" - "InboxProvider re-exports dkv.types.ts shapes using export type {} (isolatedModules requirement)" - "EWS attachment download: attachment.Load() then Buffer.from(attachment.Content) — no streaming interface available in ews-javascript-api" - "ExchangeInboxProvider buildService() uses WebCredentials with empty string fallback (avoids undefined credential error when username/password absent)" metrics: duration: 5min completed: "2026-06-27T00:05:00Z" tasks: 3 files_created: 6 files_modified: 0 --- # Phase 07 Plan 02: DKV Inbox Access Layer — Interface + IMAP + Exchange Providers Summary Interchangeable inbox-access layer built: InboxProvider interface contract, ImapProvider (imapflow with fetchAll-before-download deadlock avoidance, logger:false credential safety, 25MB PDF-bomb guard) and ExchangeInboxProvider (dynamic ews-javascript-api import, WellKnownFolderName.Inbox + FindItems — not FindAppointments, SearchFilter sender filtering, same 25MB guard). All three class-validator DTOs defined with security constraints from Research V5. ## Tasks Completed | Task | Name | Commit | Key Files | |------|------|--------|-----------| | 1 | InboxProvider interface + config/vehicle/history DTOs | 86ec896 | inbox-provider.interface.ts, dkv-config.dto.ts, dkv-vehicle.dto.ts, dkv-history.dto.ts | | 2 | ImapProvider (imapflow) | b3f21a8 | providers/imap.provider.ts | | 3 | ExchangeInboxProvider (ews-javascript-api) | 924de76 | providers/exchange-inbox.provider.ts | ## Verification Results - `pnpm --filter @tessera/api type-check` exits 0: PASS - `grep -q "fetchAll"` imap.provider.ts: PASS - `grep -q "logger: false"` imap.provider.ts: PASS - `grep -q "WellKnownFolderName.Inbox"` exchange-inbox.provider.ts: PASS - `grep -q "import('ews-javascript-api')"` exchange-inbox.provider.ts: PASS - `grep -q "IsEmail"` dkv-config.dto.ts: PASS - `grep -q "InboxProvider"` inbox-provider.interface.ts: PASS - Both providers > 40 lines: imap.provider.ts (224 lines), exchange-inbox.provider.ts (228 lines): PASS - No download() inside fetch() async iterator: PASS (fetchAll pattern used) - 25MB attachment size guard present in both providers: PASS ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] export type required for isolatedModules TypeScript setting** - **Found during:** Task 1 type-check - **Issue:** `inbox-provider.interface.ts` used `export { InboxAttachment, InboxConfig, InboxEmail }` (value export syntax). The TypeScript project has `isolatedModules: true`, which requires `export type { ... }` for type-only re-exports (TS1205 error). - **Fix:** Changed to `export type { InboxAttachment, InboxConfig, InboxEmail }` - **Files modified:** apps/api/src/dkv/providers/inbox-provider.interface.ts - **Commit:** 86ec896 ### EWS Adjustments vs Calendar ExchangeProvider | Aspect | CalendarExchangeProvider | ExchangeInboxProvider | |--------|--------------------------|----------------------| | Folder | `WellKnownFolderName.Calendar` | `WellKnownFolderName.Inbox` | | Find method | `FindAppointments` + `CalendarView` | `FindItems` + `ItemView` | | Bind method | Direct from findResults.Items | `EmailMessage.Bind` with PropertySet | | Sender filter | Not applicable (calendar) | `SearchFilter.ContainsSubstring` (server) + client-side verification | | Attachment handling | Not applicable (calendar) | `FileAttachment.Load()` + `Content` → Buffer | | Error path | returns `[]` | returns `[]` (consistent) | ## Known Stubs None — all functionality in this plan is fully implemented. Neither provider contains placeholder comments or TODO items that would block Plan 04 orchestration. ## Threat Flags None. All STRIDE threats in this plan's threat register were mitigated: - T-07-03: `logger: false` in ImapFlow constructor; generic error messages in all catch blocks; credentials never appear in log strings - T-07-04: senderFilter validated with `@IsEmail()`, port with `@Min(1)@Max(65535)`, protocol/encryption with `@IsIn()` - T-07-05: `MAX_ATTACHMENT_BYTES = 25MB` guard applied before buffering in both ImapProvider (streamToBuffer) and ExchangeInboxProvider (extractPdfAttachments) - T-07-06: `DkvHistoryQueryDto` exposes `page` and `limit` with `@IsInt() @Min(1)` ## Self-Check: PASSED Files verified present: - apps/api/src/dkv/providers/inbox-provider.interface.ts ✓ - apps/api/src/dkv/providers/imap.provider.ts ✓ - apps/api/src/dkv/providers/exchange-inbox.provider.ts ✓ - apps/api/src/dkv/dto/dkv-config.dto.ts ✓ - apps/api/src/dkv/dto/dkv-vehicle.dto.ts ✓ - apps/api/src/dkv/dto/dkv-history.dto.ts ✓ Commits verified in git log: - 86ec896 ✓ (feat(07-02): InboxProvider interface + config/vehicle/history DTOs) - b3f21a8 ✓ (feat(07-02): ImapProvider — IMAP inbox access via imapflow) - 924de76 ✓ (feat(07-02): ExchangeInboxProvider — EWS email inbox access)