# Project Briefing — OutlookAddin **Purpose of this document:** self-contained briefing for a fresh Claude Code session (or any new developer) picking up work on this project. Read this before doing anything. --- ## TL;DR A **Windows VSTO/COM Outlook Add-in** (C# / .NET Framework 4.8) that lets 10 lawyers at marcus-law.co.il file emails from Outlook desktop into EspoCRM. The server-side ([MailRouter v1.2.0](https://gitea.dev.marcus-law.co.il/espocrm-extensions/MailRouter)) is already deployed and ready. **This repo is the client.** Target Outlook versions: **Microsoft 365, Office 2021, Office 2019** (not 2016). The team uses **IMAP-connected Gmail mailboxes** — this is why Office.js Web Add-ins won't work and we went VSTO. --- ## The four features the Add-in must deliver 1. **File existing email to case/contact** — Ribbon button in Explorer; opens a WPF dialog with live autocomplete; POSTs the selected mail(s) with attachments to `POST /api/v1/MailRouter/file`; categorizes filed mail with a colored Outlook category. 2. **Reading-pane sidebar** — `CustomTaskPane` docked right (320px). On selection change, looks up the sender via `GET /api/v1/EmailAddress/search?q=&entityType=Contact` and shows matched Case/Contact cards with "Open in EspoCRM", "File to this", "File elsewhere". 3. **Compose from Case (bidirectional)** — - **Outlook → EspoCRM:** Inspector ribbon button "Compose from Case" → picker → fills To/Subject/footer from `GET /api/v1/Case/`; stamps `MailItem.UserProperties["EspoCaseId"]`. - **EspoCRM → Outlook:** URL protocol `outlookaddin://compose?caseId=` registered at install (HKCU); a tiny handler exe pipes the request to the running Add-in. - **ItemSend handler:** when EspoCaseId is set, auto-file the sent message to that case (with 60s fallback for IMAP latency in Sent Items sync). 4. **Auto-sync folders** — Settings tab "Folders" with TreeView + checkboxes. `FolderWatcher` subscribes to `Items.ItemAdd` on watched folders (must hold `Items` references in a static field — VSTO footgun!). New mail → `MatchingService` → confident match → auto-file, else categorize "Needs filing" + toast. --- ## Server-side contract (already deployed — DO NOT MODIFY) ### `POST /api/v1/MailRouter/file` Authoritative source: [`MailRouter/files/.../FileEmailService.php:9-26`](https://gitea.dev.marcus-law.co.il/espocrm-extensions/MailRouter/src/branch/main/files/custom/Espo/Modules/MailRouter/Classes/Service/FileEmailService.php#L9-L26). ```json { "messageId": "", "subject": "string", "from": "Name ", "to": ["addr1", "addr2"], "cc": ["..."], "bcc": ["..."], "replyTo": ["..."], "body": "string (plain or HTML)", "isHtml": true, "dateSent": "2026-05-11T08:30:00Z", "parentType": "Case", "parentId": "", "attachments": [ { "name": "doc.pdf", "contentType": "application/pdf", "contentBase64": "..." } ] } ``` Response: ```json { "id": "", "created": true, "parentType": "Case", "parentId": "", "messageId": "" } ``` - Dedupes by `messageId` (set parent on existing record, returns `created: false`). - `parentType` ∈ `{ Case, Lead, Contact, Account }`. - 401 Forbidden if not authenticated. - 400 BadRequest if `parentType`/`parentId` missing. ### Other endpoints the Add-in uses | Endpoint | Purpose | |---|---| | `GET /api/v1/GlobalSearch?q=` | Free-text autocomplete across Case/Contact/Lead/Account | | `GET /api/v1/EmailAddress/search?q=&entityType=Contact` | Find contact by email address | | `GET /api/v1/Case?q=&maxSize=20` | Entity-specific autocomplete | | `GET /api/v1/Case/?select=name,number,contactsIds,accountId` | Fetch case for compose flow | | `GET /api/v1/Account/` | Follow account from contact | | `GET /api/v1/Case?where[0][type]=linkedWith&where[0][attribute]=contacts&where[0][value]=&maxSize=5` | Recent cases for contact | | `GET /api/v1/App/user` | Test connection (returns current user) | ### Authentication ``` Espo-Authorization: ``` Each lawyer gets an API key issued by the admin via EspoCRM Admin → API Users (or per-user via User Profile). Stored locally encrypted with DPAPI. ### Environments | Env | Base URL | |---|---| | dev | https://espocrm.dev.marcus-law.co.il | | prod | https://crm.prod.marcus-law.co.il | --- ## Tech stack (locked, do not relitigate) | Concern | Pick | |---|---| | Runtime | **.NET Framework 4.8** (VSTO Runtime targets desktop CLR, .NET 6+ VSTO is rough) | | UI | **WPF** (in `ElementHost` inside WinForms `UserControl` for CustomTaskPane) | | HTTP | `HttpClient` + **Polly** (Timeout 15s → Retry 3 exp → CircuitBreaker 5/30s) | | JSON | `System.Text.Json` (NuGet polyfill on 4.8) | | Logging | **Serilog** + `Serilog.Sinks.File` daily rolling, 14-day retention | | MVVM | `CommunityToolkit.Mvvm` (source generators) | | Tests | xUnit + Moq + FluentAssertions | | DI | Manual composition root in `ThisAddIn_Startup` (no container) | | Settings | JSON in `%APPDATA%\MarcusLaw\OutlookAddin\settings.json` | | Secrets | DPAPI in `%APPDATA%\MarcusLaw\OutlookAddin\creds.dat`, entropy = `sha256("MarcusLaw.OutlookAddin.v1")` | | Logs | `%LOCALAPPDATA%\MarcusLaw\OutlookAddin\logs\YYYY-MM-DD.log` | Namespace root: `MarcusLaw.OutlookAddin.*` --- ## Repo location | | | |---|---| | Gitea | `https://gitea.dev.marcus-law.co.il/espocrm-extensions/OutlookAddin` | | Clone URL (HTTPS w/ token) | `https://chaim:19771681b7dc9939e4e597f7dae3a2bad87f4084@gitea.dev.marcus-law.co.il/espocrm-extensions/OutlookAddin.git` | | Local (Windows) | `\OutlookAddin\` (currently `C:\Users\\source\repos\OutlookAddin`) | | Local (Linux dev server) | `/home/chaim/espocrm-extensions/OutlookAddin/` | The local-dev paths in this doc reference the **Linux server** (`/home/chaim/...`) because the initial scaffold was created there. After cloning to Windows, paths become `C:\Users\\source\repos\OutlookAddin\...`. --- ## Solution structure (planned) ``` OutlookAddin.sln ├── src\ │ ├── OutlookAddin\ ← VSTO host (.NET FW 4.8, legacy csproj from VS template) │ │ ├── ThisAddIn.cs composition root, ItemSend/ItemAdd wiring │ │ ├── Ribbon\{Main,Compose}Ribbon.{xml,cs} │ │ ├── TaskPanes\ReadingPane{Control.cs,View.xaml} │ │ ├── EventHandlers\{ItemSendHandler,FolderWatcher,ExplorerSelectionHandler}.cs │ │ └── UrlProtocol\OutlookAddinProtocolHandler.cs │ ├── OutlookAddin.Core\ ← pure .NET (Office-free, testable on Linux) │ │ ├── Models\ DTOs │ │ ├── Services\{EspoCrmClient,FilingService,MatchingService,DiskRetryQueue}.cs │ │ ├── Security\DpapiCredentialStore.cs │ │ ├── Configuration\{AppSettings,SettingsManager}.cs │ │ └── Logging\LoggerFactory.cs │ ├── OutlookAddin.UI\ ← WPF dialogs + ViewModels │ │ ├── Dialogs\{FileToCase,Settings,ComposeFromCase}Dialog.xaml │ │ ├── ViewModels\ │ │ ├── Controls\{AutocompleteTextBox,RecentEntitiesList}.cs │ │ ├── Localization\{Strings.he.resx,Strings.en.resx,LocalizationManager.cs} │ │ └── Themes\{Generic,RtlFonts}.xaml │ └── OutlookAddin.Tests\ ← xUnit ├── installer\OutlookAddin.Setup\ ← WiX / ClickOnce manifest (week 6) ├── docs\ │ ├── ARCHITECTURE.md ← component diagram + threading + per-feature flow │ ├── ONBOARDING.md ← Hebrew lawyer-facing install guide │ └── PROJECT-BRIEFING.md ← this file └── .gitea\workflows\build.yml ← Windows runner CI (deferred — local builds for now) ``` --- ## Current state of the repo ### ✅ Done (week 0 — infrastructure) 1. Gitea repo created (private, `espocrm-extensions/OutlookAddin`) 2. Directory tree scaffolded 3. `OutlookAddin.sln` + 3 SDK-style csproj (`Core`, `UI`, `Tests`) — all load cleanly in VS 2022 4. `OutlookAddin.csproj` (host) — **hand-written legacy csproj is INCOMPATIBLE; VS marks it "Unsupported"**. **MUST be regenerated from the VS template.** 5. `.gitignore`, `README.md`, `docs/ARCHITECTURE.md`, `docs/ONBOARDING.md` 6. `.gitea/workflows/build.yml` stub (won't run until Windows runner exists) 7. Infisical folders `/outlook-addin` created in dev/prod/nautilus (empty — secrets added when needed) 8. Task Master initialized at `.taskmaster/` with **9 tasks** matching the 6-week plan, using `provider=claude-code` (free) 9. Initial 2 commits pushed to Gitea `main` ### 🚧 In progress — first thing to do on Windows **Recreate the VSTO host project from the official template.** Step-by-step: 1. Open `OutlookAddin.sln` in VS 2022. 2. When you see "Review Project And Solution Changes" → "Unsupported" → click OK (VS loads the other 3 projects). 3. Solution Explorer → right-click `OutlookAddin` (unloaded/missing) → **Remove** (not Delete — Remove just unlists it). 4. **Delete the stale source files** the Linux scaffold created. From a Git Bash / PowerShell at the repo root: ```bash rm -rf src/OutlookAddin ``` (or in Windows Explorer: delete the `src\OutlookAddin\` folder entirely.) 5. `File` → `Add` → `New Project...` → search **"outlook"** → select **Outlook VSTO Add-in** (C#) → Next. 6. Configure: - **Project name:** `OutlookAddin` (exact — not `MarcusLaw.OutlookAddin`) - **Location:** `\src\` (the parent `src` folder) - **Solution:** Add to solution - **Framework:** **.NET Framework 4.8** - Click Create. 7. VS generates `src\OutlookAddin\OutlookAddin.csproj` + `ThisAddIn.cs` + `ThisAddIn.Designer.cs` + `app.config` + `Properties\AssemblyInfo.cs`. 8. **Set the assembly metadata** — open `Properties\AssemblyInfo.cs` and set: - `[assembly: AssemblyCompany("Marcus-Law")]` - `[assembly: AssemblyProduct("OutlookAddin")]` - Root namespace: right-click project → Properties → Application → **Default namespace = `MarcusLaw.OutlookAddin`**, **Assembly name = `MarcusLaw.OutlookAddin`** 9. Right-click `OutlookAddin` → Add → Project Reference... → check `OutlookAddin.Core` and `OutlookAddin.UI` → OK. 10. **Build Solution (Ctrl+Shift+B)** — expect success. 11. **F5** — VS should launch Outlook with the Add-in loaded (no UI yet, but it shouldn't crash). Close Outlook to return to VS. 12. **Commit + push** the regenerated host project: ```bash git add src/OutlookAddin git commit -m "chore: regenerate OutlookAddin host project from VSTO template" git push ``` ### 📋 After that — start Task #2 (Core services) Implement `OutlookAddin.Core.Services.EspoCrmClient` per the design in [`ARCHITECTURE.md`](ARCHITECTURE.md) and per the API contracts in this doc. See Task Master task #2 for the full breakdown: ```bash task-master show 2 ``` --- ## Plan file (long-term) Full 6-week implementation plan with risks, verification steps, and the deployment strategy: `/home/chaim/.claude/plans/resilient-sauteeing-feather.md` (on the Linux dev server). A copy in this repo is **not** committed because the plan is workspace-wide context. If a Claude session needs it, it can be fetched via SSH: ```bash scp chaim@192.168.10.206:/home/chaim/.claude/plans/resilient-sauteeing-feather.md . ``` --- ## Important repo conventions 1. **Conventional commits**: `fix:`, `feat:`, `chore:`, `docs:`, `refactor:`, `test:`. 2. **Co-Author footer**: every Claude-assisted commit ends with ``` Co-Authored-By: Claude Opus 4.7 (1M context) ``` 3. **No emojis** in commits or code unless explicitly requested by the user. 4. **No backwards-compat shims** — change code directly, no `// removed` comments, no renamed-with-underscore unused vars. 5. **No comments explaining WHAT** — only WHY when the why is non-obvious (hidden constraints, workarounds for bugs, surprising behavior). 6. **Default to no comments at all.** 7. **Branching:** `main` is protected; feature branches `feat/` or `fix/`. 8. **Task Master:** every code change should correspond to a Task Master task. See `task-master list` to find the next task. --- ## Secrets / infrastructure references | What | Where | |---|---| | EspoCRM API keys (per-lawyer) | Issued by EspoCRM admin; stored locally in Add-in's DPAPI store | | Code-signing cert (TBD — week 6) | Infisical `/outlook-addin/CODESIGN_THUMBPRINT`, `CODESIGN_PFX_BASE64`, `CODESIGN_PASSWORD` | | ClickOnce deploy SSH key (TBD) | Infisical `/outlook-addin/PLATFORM_DEV_SSH_KEY` | | Gitea HTTPS token | `19771681b7dc9939e4e597f7dae3a2bad87f4084` (chaim user, in this doc URL above) | | MailRouter source (read-only ref) | `https://gitea.dev.marcus-law.co.il/espocrm-extensions/MailRouter` | | Infisical Project ID | `9a77b161-f70c-4dd3-9d67-b7ab850cef51` | | Infisical envs | dev (Windows runner / dev server), prod (Hetzner), nautilus | --- ## How to brief a new Claude session Tell the new session: > Read `docs/PROJECT-BRIEFING.md` in this repo. Then read `docs/ARCHITECTURE.md`. Then run `task-master list` to see open tasks. Don't start coding until you've read both docs and confirmed which task you're working on with me. If the session is on the Linux dev server (`/home/chaim/espocrm-extensions/OutlookAddin`), also have it check the long-term plan at `~/.claude/plans/resilient-sauteeing-feather.md`. --- ## How releases work (current, 2026-05-24) **Production install URL:** `https://platform.dev.marcus-law.co.il/outlook-addin/OutlookAddin.vsto` **Release flow:** 1. Code change merged to `main` (push or PR-merge — no CI fires yet) 2. Bump `` in `src/OutlookAddin/OutlookAddin.csproj` and `AssemblyVersion` + `AssemblyFileVersion` in `src/OutlookAddin/Properties/AssemblyInfo.cs` to the next 4-part version (e.g. `1.2.1.0`). CI overrides `ApplicationVersion` from the tag at build time, but leaves AssemblyInfo alone — keep them in sync manually so the bundled DLL's FileVersion matches the manifest. 3. `git tag -a v1.2.1 -m "..." && git push --tags` 4. Gitea Actions workflow (`.gitea/workflows/build.yml`) fires: - Imports the **real** codesign cert from secrets `CODESIGN_CERT_PFX_BASE64` + `CODESIGN_THUMBPRINT` (publicKeyToken `c68d2b4c25051c5b`, `CN=Marcus-Law OutlookAddin`) - Patches csproj to swap the dev self-signed thumbprint (`2399E9BF…`) for the real one - `msbuild /t:Publish` with `/p:InstallUrl=https://platform.dev.marcus-law.co.il/outlook-addin/` and `/p:ApplicationVersion=.0` - Uploads `publish.tar.gz` to Gitea generic registry (`outlook-addin-publish//publish.tar.gz`) - Linux job downloads the tar.gz, builds a Docker image `outlook-addin-cdn:latest`, pushes to Gitea registry, calls Coolify to redeploy the CDN container - Posts to Mattermost channel `git-verelasim` with the install URL on success / a failure link on failure 5. Lawyers' installed Add-in checks the deployment manifest at the install URL every 7 days (configured in csproj `7`) or whenever they click "בדוק עדכונים" in Settings → אודות. **Semver policy:** PATCH for bug fixes (unilateral), MINOR for visible features (unilateral when warranted), MAJOR only with Chaim's explicit approval. **Branding (`KlearBranding` repo):** the product is presented to lawyers as **Klear** — title bars, MessageBox titles, ribbon group label, About header all say "Klear". The logo is mirrored from `espocrm-extensions/KlearBranding/files/client/custom/img/logo-klear.png` into `src/OutlookAddin.UI/Resources/logo-klear.png` and embedded as a WPF Resource. Brand accent color: `#121d2e` (dark navy). **What stays MarcusLaw under the hood (deliberate — preserves auto-update across the rebrand):** - ClickOnce app identity (`AssemblyName=MarcusLaw.OutlookAddin`) - `%APPDATA%\MarcusLaw\OutlookAddin\settings.json` and `creds.dat` - `%LOCALAPPDATA%\MarcusLaw\OutlookAddin\logs\` - `DpapiCredentialStore.EntropySeed = "MarcusLaw.OutlookAddin.v1"` - Code namespaces `MarcusLaw.OutlookAddin.*` - Named pipe `MarcusLaw.OutlookAddin` Changing any of these would orphan all existing installs and require a coordinated uninstall+reinstall on every lawyer's PC plus an entropy migration for DPAPI-encrypted credentials. See the LegalCRM rebrand misadventure in git history for what *not* to do. **Known traps when releasing:** - VS Publish wizard (Project Properties → Publish) **rewrites** csproj at every click — flips `` back to `true`, resets `` to whatever's in the UI, sometimes deletes prior `Application Files/<...>/` bundles. **Don't use it.** Tag and let CI do it. - There's an old `Publish/` directory in git history that was a parallel manual-release channel via `gitea.../raw/branch/main/Publish/`. It's **not the production install URL** — that's platform.dev. The folder was dropped in v1.2.0. ## Things deliberately deferred | What | When | Why deferred | |---|---|---| | Identity-level rebrand to a pure Klear product (AssemblyName, namespaces, DPAPI entropy, AppData paths) | Until there's another reason to force-reinstall on all 10 PCs | Visible rebrand already done in v1.2.0 without disrupting users; full rename trades aesthetic cleanliness for a day of IT coordination + lost credentials | --- ## Open risks (from the plan) - VSTO `Items` event handler GC unsubscribe (folder watcher footgun) — must hold `Items` refs in **static** field. - IMAP `PR_INTERNET_MESSAGE_ID` race on Sent Items — use UserProperty GUID + 60s watcher fallback. - Outlook HTML quirks (VML, conditional comments) — send both plain `Body` and `HTMLBody`, let server decide. - API key rotation → silent 401 — show non-dismissable modal, log key fingerprint hash. - Base64-of-50MB-attachment memory blowup — sequential processing, 25MB single-attachment cap. - Hebrew RTL in subject prefix `[2025-123]` — use Unicode LRM/RLM bidi marks. See full risk table in `ARCHITECTURE.md` and the plan file.