HR-ATS-Portal/Sync_write_request.md

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: 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