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>
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
# 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/<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`](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": "<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:
|
||||
```json
|
||||
{ "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:
|
||||
```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:** `<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:
|
||||
```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) <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.
|
||||
Reference in New Issue
Block a user