2.9 KiB
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}:
{
"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: truewhen already true is a no-op200with the current record. - Must not advance or disturb the delta cursor. This is a point write, not a
sync round. Cursor, watcher, and
/changesbuffer stay untouched. - 404
ErrorItemNotFoundfor 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-statuscontract - Push / Graph change-notification subscriptions
- Writing any field other than
isRead