87 lines
2.9 KiB
Markdown
87 lines
2.9 KiB
Markdown
# Email service — write read-status (`PATCH /sync/read-status/...`)
|
|
|
|
Copy everything below the line into any LLM session (or hand it to whoever owns the
|
|
email microservice) before implementing the write endpoint.
|
|
|
|
---
|
|
|
|
You are extending the **email microservice** that already exposes the read-status
|
|
delta and point-lookup APIs documented in `Sync_read.md`. Implement a **write**
|
|
path that marks a message read (or unread) in the signed-in user's Outlook mailbox
|
|
via Microsoft Graph. Mirror the existing `/sync/read-status/*` style exactly —
|
|
same bearer auth, same `:path` id handling, same response shape.
|
|
|
|
## Why we need this
|
|
|
|
The HR-ATS inbox app learns that a user opened a message before Outlook does.
|
|
Today that signal dies in our database: we have no Graph write permission and the
|
|
email service exposes no write endpoint. Without this PATCH, local mark-read and
|
|
Outlook drift permanently (and a later delta can even revert our flag).
|
|
|
|
## Requested contract
|
|
|
|
Mirror the existing read endpoints so both parse with one code path:
|
|
|
|
```
|
|
PATCH /sync/read-status/message/{id}
|
|
Authorization: Bearer <api token>
|
|
Content-Type: application/json
|
|
|
|
{ "isRead": true }
|
|
```
|
|
|
|
**200 response** — identical shape to `GET /sync/read-status/message/{id}`:
|
|
|
|
```jsonc
|
|
{
|
|
"id": "AAMk…",
|
|
"isRead": true,
|
|
"lastModifiedDateTime": "2026-08-07T10:14:52Z",
|
|
"subject": "Invoice #421"
|
|
}
|
|
```
|
|
|
|
Same `:path` converter for Graph ids that contain `/`, `+`, or `=`. Same bearer
|
|
auth as every other `/sync/*` route. Answer `401` until device-code sign-in
|
|
completes.
|
|
|
|
## Required behaviour
|
|
|
|
- **Idempotent.** Re-PATCHing `isRead: true` when already true is a no-op `200`
|
|
with the current record.
|
|
- **Must not advance or disturb the delta cursor.** This is a point write, not a
|
|
sync round. Cursor, watcher, and `/changes` buffer stay untouched.
|
|
- **404 `ErrorItemNotFound`** for unknown or deleted ids (same as the GET).
|
|
- **403 surfaced distinctly** if the Graph scope is missing, so callers can tell
|
|
"not permitted" from "not found".
|
|
|
|
## Graph scope prerequisite
|
|
|
|
Needs `Mail.ReadWrite`. The service currently signs in read-only. Treat upgrading
|
|
the consent / device-code scopes as an explicit product decision before shipping
|
|
the route — not an implementation footnote.
|
|
|
|
## Optional batch form
|
|
|
|
For bulk reconcile without N round-trips:
|
|
|
|
```
|
|
PATCH /sync/read-status/messages
|
|
{ "ids": ["AAMk…", "AAMk…"], "isRead": true }
|
|
```
|
|
|
|
Return a list of the same per-message records (or per-id errors). Nice-to-have;
|
|
the single-id PATCH is the hard requirement.
|
|
|
|
## What the caller will do with it
|
|
|
|
HR-ATS will enqueue one Taskiq task per human mark-read, retried via existing
|
|
smart-retry middleware. Expected volume is low (opens, not sweeps). After this
|
|
lands we will stop treating local-only mark-read as a known divergence.
|
|
|
|
## Out of scope for this request
|
|
|
|
- Changing the delta `/sync/read-status` contract
|
|
- Push / Graph change-notification subscriptions
|
|
- Writing any field other than `isRead`
|