This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OutlookAddin/docs/PROJECT-BRIEFING.md
T
chaim 485056d46b docs: add PROJECT-BRIEFING.md for handoff to Windows VS session
Self-contained briefing for a fresh Claude Code session picking up work
on this project from a Windows machine running VS 2022. Covers:

- TL;DR + 4 features
- MailRouter API contract + EspoCRM endpoints
- Tech stack (locked decisions)
- Repo layout + current state
- Step-by-step recipe to regenerate the VSTO host project from the
  template (the hand-written csproj is incompatible with VS)
- Conventions + deferred work + open risks

Read this before doing anything: docs/PROJECT-BRIEFING.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:11:45 +00:00

15 KiB

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) 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 sidebarCustomTaskPane 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/<id>; stamps MailItem.UserProperties["EspoCaseId"].
    • EspoCRM → Outlook: URL protocol outlookaddin://compose?caseId=<id> 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.

{
  "messageId": "<rfc2822-id@server>",
  "subject":  "string",
  "from":     "Name <addr>",
  "to":       ["addr1", "addr2"],
  "cc":       ["..."],
  "bcc":      ["..."],
  "replyTo":  ["..."],
  "body":     "string (plain or HTML)",
  "isHtml":   true,
  "dateSent": "2026-05-11T08:30:00Z",
  "parentType": "Case",
  "parentId":   "<entity-id>",
  "attachments": [
    { "name": "doc.pdf", "contentType": "application/pdf", "contentBase64": "..." }
  ]
}

Response:

{ "id": "<emailId>", "created": true, "parentType": "Case", "parentId": "<id>", "messageId": "<msgId>" }
  • 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=<text> Free-text autocomplete across Case/Contact/Lead/Account
GET /api/v1/EmailAddress/search?q=<addr>&entityType=Contact Find contact by email address
GET /api/v1/Case?q=&maxSize=20 Entity-specific autocomplete
GET /api/v1/Case/<id>?select=name,number,contactsIds,accountId Fetch case for compose flow
GET /api/v1/Account/<id> Follow account from contact
GET /api/v1/Case?where[0][type]=linkedWith&where[0][attribute]=contacts&where[0][value]=<contactId>&maxSize=5 Recent cases for contact
GET /api/v1/App/user Test connection (returns current user)

Authentication

Espo-Authorization: <base64(username:apiKey)>

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) <user-chosen>\OutlookAddin\ (currently C:\Users\<user>\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\<user>\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:
    rm -rf src/OutlookAddin
    
    (or in Windows Explorer: delete the src\OutlookAddin\ folder entirely.)
  5. FileAddNew Project... → search "outlook" → select Outlook VSTO Add-in (C#) → Next.
  6. Configure:
    • Project name: OutlookAddin (exact — not MarcusLaw.OutlookAddin)
    • Location: <repo>\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:
    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 and per the API contracts in this doc. See Task Master task #2 for the full breakdown:

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:

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) <noreply@anthropic.com>
    
  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/<short-name> or fix/<short-name>.
  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.


Things deliberately deferred

What When Why deferred
Windows CI runner on Coolify Until ClickOnce publish becomes useful Local builds on dev's VS 2022 suffice through week 5
Code signing cert (self-signed) + GPO push to 10 PCs Week 6 (before release) IT task; cert isn't useful until we have something to sign
ClickOnce hosting setup at platform.dev.marcus-law.co.il/outlook-addin/ Week 6 No build artifacts yet
Mattermost release notification webhook in CI Week 6 Coupled to CI runner

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.