Originally created by: Waqasahmedwaseer
v1.1 Inbox — Commit 2 of 6: ingestion + REST API. No UI (Commit 4), no SSE wiring (Commit 3). Design: PR [#110] §4–5.
InboxIngestService hooked into POST /internal/webhook-event/:wid alongside the existing webhook fan-out (doesn't replace it). Idempotent on (workspace, waMessageId); 1:1 only (groups skipped); upserts Contact (whatsappName ← pushName, savedName untouched) + Conversation; session.deleted/logged_out → sessionDeletedAt archive; messages.update → delivery status.| Method | Path |
|---|---|
| GET | /workspaces/:wid/conversations?status=&cursor=&limit=&q= |
| GET | /workspaces/:wid/conversations/:cid |
| GET | /workspaces/:wid/conversations/:cid/messages?cursor=&limit= |
| PATCH | /workspaces/:wid/conversations/:cid (status / tags) |
| POST | /workspaces/:wid/conversations/:cid/read |
| POST | /workspaces/:wid/conversations/:cid/messages (reply → WA Server) |
InboxEventsService emits for the Commit 3 SSE layer.# inbound (what wa-server POSTs) → creates a conversation
curl -X POST $API/internal/webhook-event/$WID -H "x-internal-secret: $SEC" -H 'Content-Type: application/json' \
-d '{"event":"message.received","sessionId":"support","timestamp":"...","data":{"messageId":"WAID","from":"447700900123@s.whatsapp.net","isGroup":false,"type":"conversation","content":{"text":"Hi"},"message":{"key":{"remoteJid":"447700900123@s.whatsapp.net","fromMe":false,"id":"WAID"},"pushName":"Ahmed"}}}'
# list / thread / patch / read / reply (Bearer JWT)
curl $API/workspaces/$WID/conversations -H "Authorization: Bearer $JWT"
curl "$API/workspaces/$WID/conversations/$CID/messages?limit=50" -H "Authorization: Bearer $JWT"
curl -X PATCH $API/workspaces/$WID/conversations/$CID -H "Authorization: Bearer $JWT" -d '{"status":"RESOLVED","tags":["vip"]}'
curl -X POST $API/workspaces/$WID/conversations/$CID/read -H "Authorization: Bearer $JWT"
curl -X POST $API/workspaces/$WID/conversations/$CID/messages -H "Authorization: Bearer $JWT" -d '{"text":"Hi back!"}'
| # | Criterion | Result |
|---|---|---|
| 1 | inbound → conversation appears (name Ahmed, unread 1, preview set) |
✅ |
| 2 | re-deliver same message → no duplicate (messages stays 1) | ✅ |
| 3 | reply with no WA server → 400/503 (offline handled) |
✅ |
| 4 | same contact, different session → 2 separate conversations | ✅ |
| 5 | PATCH status/tags → reflected in next GET | ✅ |
| 6 | mark read → unreadCount 0 | ✅ |
| + | IDOR (other workspace) → 403 · group @g.us skipped · search · cursor pagination |
✅ |
Test 1/3's upstream leg (phone → WhatsApp → wa-server) needs a live session and your phone; I exercised the dashboard-api's real ingestion path by POSTing the exact payload wa-server sends. The reply→phone delivery needs a connected session (verified the offline 503/400 path here).
main.ts does require('express') but express isn't a declared dependency → node dist/main.js breaks under pnpm (works in Docker). Worth adding express to deps.generator output = "../node_modules/.prisma/client" generates to the package dir, but @prisma/client resolves the copy in the .pnpm store → stale client at runtime. Needs a postinstall/generate alignment.main was behind origin (Commit 1 merge wasn't pulled) — rebased this branch onto origin/main.~1.5 sessions. GET /workspaces/:wid/inbox/stream (text/event-stream), cookie auth, heartbeat, connection caps (10/ws · 3/user · idle+sweep per design §7), subscribing to the InboxEventsService already emitting here.
v1.1 Inbox — Commit 2/6. Main stays deployable. 🤖 Generated with Claude Code
Ticket changed by: Waqasahmedwaseer