# OutlookAddin — Architecture

תוסף VSTO/COM ל-Outlook Desktop שמדבר עם EspoCRM. ראה גם [`resilient-sauteeing-feather.md`](../../.claude/plans/resilient-sauteeing-feather.md) לתכנית המלאה.

## Component diagram

```
┌──────────────────────────────────────────────────────────────────────┐
│  Outlook Desktop (Win, Office 2019/2021/M365)                        │
│                                                                       │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │  MarcusLaw.OutlookAddin (.dll, VSTO host)                       │ │
│  │  ├── Ribbon (Explorer + Inspector)                              │ │
│  │  ├── CustomTaskPane (Reading-pane sidebar)                      │ │
│  │  ├── EventHandlers (ItemSend, ItemAdd, SelectionChange)         │ │
│  │  └── UrlProtocol (outlookaddin:// listener via named pipe)      │ │
│  └────────────────────────────────────────────────────────────────┘ │
│             │ uses ↓                                                  │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │  MarcusLaw.OutlookAddin.UI (WPF dialogs + VMs)                  │ │
│  │  FileToCaseDialog · SettingsDialog · ComposeFromCaseDialog      │ │
│  │  ReadingPaneView · LocalizationManager · RTL themes             │ │
│  └────────────────────────────────────────────────────────────────┘ │
│             │ uses ↓                                                  │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │  MarcusLaw.OutlookAddin.Core (pure .NET 4.8, Office-free)       │ │
│  │  EspoCrmClient (HttpClient + Polly)                             │ │
│  │  FilingService · MatchingService · DiskRetryQueue               │ │
│  │  DpapiCredentialStore · SettingsManager · LoggerFactory         │ │
│  └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────────┘
                           │ HTTPS
                           ↓
              ┌────────────────────────────┐
              │  EspoCRM (PHP)             │
              │  + MailRouter v1.2.0       │
              │  POST /MailRouter/file     │
              │  GET  /EmailAddress/search │
              │  GET  /GlobalSearch        │
              │  GET  /Case, /Account, ... │
              └────────────────────────────┘
```

## Threading model

Outlook COM is **STA**. The single Outlook UI thread must own every COM object reference. Background work (HTTP, base64 encoding) must run on the threadpool, and any callback that touches Outlook must marshal back via the captured `SynchronizationContext`.

**The golden rule:** never touch a COM object from inside a `Task.Run` continuation. Either:
- Read primitive values (string, int, byte[]) from the COM object on the UI thread first, pass those to `Task.Run`, or
- Use `await syncContext.PostAsync(() => mailItem.Categories = ...)` to marshal back.

`Globals.ThisAddIn.Application` is captured in `ThisAddIn_Startup` and exposed via `IOutlookAccess` wrapper.

## Per-feature flow

### File to EspoCRM (feature 1)

```
User clicks Ribbon "File to EspoCRM"
  → FileToCaseDialog opens (modal, WPF, RTL)
  → User searches: GET /GlobalSearch?q=... debounced 250ms
  → User picks target, dialog returns (parentType, parentId)
  → FilingService.FileAsync(MailItem[], target) on threadpool:
      for each MailItem:
        - extract on STA: subject, from, to/cc/bcc, body, dateSent
        - extract PR_INTERNET_MESSAGE_ID via PropertyAccessor
        - for each Attachment: SaveAsFile → read bytes → base64 → delete temp
        - POST /MailRouter/file (Polly retries)
        - 200/201 → marshal back: MailItem.Categories += "Filed: <name>"
        - 409 → success (already filed)
        - 401 → modal "re-enter API key"
        - else → DiskRetryQueue
  → Toast: "Filed 3 of 4 emails. 1 queued."
```

### Reading-pane sidebar (feature 2)

```
Explorer.SelectionChange → debounce 200ms
  → ExplorerSelectionHandler grabs MailItem.SenderEmailAddress
  → MatchingService.LookupAsync(sender):
      1. GET /EmailAddress/search?q=<sender>&entityType=Contact
      2. If 1 hit: GET /Account/<contactId.accountId>
                    GET /Case?where[linkedWith]=contacts:<contactId>
      3. cache 10 min in MemoryCache
  → ReadingPaneViewModel updates → WPF binding refreshes sidebar
```

### Compose from Case (feature 3)

```
A. Outlook → EspoCRM:
   Inspector Ribbon "Compose from Case"
     → ComposeFromCaseDialog (filtered to Case)
     → GET /Case/<id>?select=name,number,contactsIds,accountId
     → set MailItem.To, .Subject, append HTMLBody footer
     → stamp UserProperty EspoCaseId = caseId, GUID = guid

B. EspoCRM → Outlook:
   User clicks "Email this case" in EspoCRM UI
     → outlookaddin://compose?caseId=<id>
     → OS launches OutlookAddinProtocolHandler.exe
     → handler writes to \\.\pipe\MarcusLaw.OutlookAddin
     → Add-in pipe listener reproduces flow A

C. ItemSend:
   MailItem sent → handler reads UserProperty EspoCaseId
   → watch Sent Items via Items.ItemAdd for matching GUID
   → when found: FilingService.FileAsync(item, case)
```

### Auto-file folders (feature 4)

```
Settings → Folders tab: TreeView of mailbox folders, checkboxes
  Each watched: default parent, mode (auto-file / notify), threshold

FolderWatcher (singleton):
  - Loads (StoreId, FolderPath, EntryID) tuples from settings.json
  - For each: store Items reference in static field (CRITICAL — prevents GC)
  - Subscribe Items.ItemAdd

On ItemAdd:
  - Capture EntryID immediately
  - Check HashSet<EntryID> dedup
  - Queue work to STA-bound TaskScheduler
  - MatchingService.LookupAsync(sender)
  - 1 hit + auto-mode → FilingService.FileAsync
  - else → Categories += "Needs filing" + toast
```

## Deployment

ClickOnce hosted at `https://platform.dev.marcus-law.co.il/outlook-addin/`. Auto-update on Outlook launch. Self-signed cert deployed to all 10 lawyer machines via GPO (Trusted Publishers + Trusted Root CA).

ראה [`resilient-sauteeing-feather.md`](../../.claude/plans/resilient-sauteeing-feather.md) § Deployment לפרטים מלאים.

## Repository conventions

- **Conventional commits** (`fix:`, `feat:`, `chore:`, `docs:`)
- **Branches:** `main` (protected, CI green required), feature branches `feat/<short-name>`
- **Versioning:** SemVer, ClickOnce auto-rebuilds on every push to `main`, formal releases tagged `v1.0.0`, `v1.1.0`, ...
- **Task Master:** each issue/feature corresponds to a `task-master` task. תהליך פר-extension כפי שב-`~/espocrm-extensions/CLAUDE.md`.
