Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions STATUS.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Helpthread β€” Status

**Current state:** the mail engine's core loop is closed end-to-end β€” an inbound reply is parsed and threaded, conversations and threads are persisted, and an outbound reply is sent with a signed reply token in its Message-ID that a future customer reply threads back on. Everything below the wire (parse β†’ thread β†’ store β†’ send) exists and is tested against real in-process Postgres; what's left is a real email provider adapter, the HTTP/API surface, and a UI. All work lands through a guarded pipeline: every PR runs typecheck, lint, tests-with-coverage, secret scanning, and CodeQL, plus AI review (CodeRabbit) and β€” for security- and threading-critical code β€” an independent Codex pass.
**Current state:** the mail engine's full loop is built and behind a native HTTP API β€” an inbound reply is parsed, threaded, and stored; an Agent can list the inbox, read a conversation, and reply; the reply is sent with a signed token in its Message-ID that a future customer reply threads back on. Everything from the wire up (parse β†’ thread β†’ store β†’ read β†’ reply β†’ send) exists and is tested against real in-process Postgres and real `Request`/`Response` objects. What's left to run it *live* is a concrete email-provider adapter and a deployment. All work lands through a guarded pipeline: every PR runs typecheck, lint, tests-with-coverage, secret scanning, and CodeQL, plus AI review (CodeRabbit) and β€” for security-, auth-, and threading-critical code β€” an independent Codex pass.

## Now

**Phase 1 — Core engine, dogfooded.** The full inbound→outbound threading loop is built; turning it toward a runnable, dogfoodable slice (a real send adapter + the conversation API).
**Phase 1 — Core engine, dogfooded.** The inbound→outbound loop and its API are done; building the first real send adapter (Gmail) — the piece that turns the tested engine into actually-sent mail.

## Done

**Foundation**
- Founding charter ([CHARTER.md](CHARTER.md)) β€” mission, principles, licensing, architecture, roadmap.
- Behavioral specs: conversation API contract (`specs/api/conversations-v1.md`), mail threading (`specs/mail/threading.md`), outbound sending (`specs/mail/sending.md`), conversation store (`specs/store/conversations.md`).
- Behavioral specs: mail threading (`specs/mail/threading.md`), outbound sending (`specs/mail/sending.md`), conversation store (`specs/store/conversations.md`), and the native Agent Inbox API (`specs/api/agent-inbox-v1.md`).
- Platform provider interfaces (`src/providers/`) β€” queue, scheduler, blob storage, inbound email, and outbound email sender β€” Vercel-first, not Vercel-only.
- Black-box acceptance fixtures (`fixtures/mail/`).
- CI/quality foundation: TypeScript (strict, NodeNext) + Biome + Vitest (v8 coverage); CI (quality + secret scan), CodeQL, OpenSSF Scorecard; branch protection requires all checks green.
Expand All @@ -20,21 +20,26 @@
- **Signed reply tokens** (`src/mail/reply-token.ts`) β€” HMAC-SHA256 tokens minted into outbound Message-IDs and verified on reply, with key rotation. The cryptographic basis for trustworthy threading.
- **RFC 5322 message-id extractor** (`src/mail/message-id.ts`) β€” comment/quoted-string-aware tokenization, shared across the engine.
- **Threading decision** (`src/mail/thread.ts`) β€” the 5-rule algorithm: a verified token routes a reply to its conversation; no valid token starts a new one; subject is never used.
- **Conversation/thread store** (`src/store/`, `src/db/`) β€” persistence on a thin, portable raw-SQL layer over PGlite (in-process Postgres) locally, the same SQL destined for Supabase. A valid token to a closed conversation reopens it; to a deleted one, the caller starts fresh.
- **Outbound send** (`src/mail/send.ts`) β€” mints the reply token into the outbound Message-ID, persists the outbound thread as an outbox item (`pending`β†’`sent`/`failed`), and hands it to an `EmailSender`. A round-trip test proves a sent reply threads back to the right conversation.
- **Conversation/thread store** (`src/store/`, `src/db/`) β€” persistence on a thin, portable raw-SQL layer over PGlite (in-process Postgres) locally, the same SQL destined for Supabase. Keyset-paginated listing; a valid token to a closed conversation reopens it, to a deleted one the caller starts fresh.
- **Outbound send** (`src/mail/send.ts`) β€” mints the reply token into the outbound Message-ID, persists the outbound thread as an outbox item (`pending`β†’`sent`/`failed`), and hands it to an `EmailSender`. Returns typed outcomes (a delivered message is never reported as failed). A round-trip test proves a sent reply threads back to the right conversation.

**Agent Inbox API v1** (`src/api/`) β€” native, framework-agnostic `Request β†’ Response` (a Vercel/Node adapter is a later thin wrapper; Node runtime, since the engine's HMAC uses `node:crypto`).
- Constant-time Bearer auth that runs *before* routing; native `{ error: { code, message } }` envelope; `Cache-Control: no-store` on every response; UUID-shape guards; a top-level catch so nothing leaks as an uncontrolled 500.
- `GET /api/v1/conversations` (inbox list, newest-activity-first, status filter, keyset cursor) Β· `GET /api/v1/conversations/{id}` (conversation + threads) Β· `POST /api/v1/conversations/{id}/replies` (derives the headers, mints + sends) Β· `PATCH /api/v1/conversations/{id}` (close/reopen).

## Next

- **A real `EmailSender` adapter** (Gmail send / Postmark / SES) β€” the first one that puts actual mail on the wire, with a wire-level test proving the Message-ID is transmitted verbatim.
- **Send idempotency + delivery worker** ([HT-16](https://resonantiq.atlassian.net/browse/HT-16)) β€” a dedup key and a worker that retries `pending`/`failed` outbound threads reusing the same Message-ID; required before `sendReply` goes behind a live retrying caller.
- **The six-operation conversation API** and an agent inbox UI (API-first, per the charter).
- **A real `EmailSender` adapter β€” Gmail** (`users.messages.send`) through the support Google Workspace account: the first thing that puts actual mail on the wire, with a wire-level test proving our Message-ID is transmitted verbatim (Gmail preserves an RFC-compliant custom Message-ID; our token is compliant by construction). Postmark/SES/Resend remain a later one-file swap if scale or deliverability demands it.
- **A deployment** β€” a thin Vercel/Node route wrapping `createInboxApi`, a real Gmail inbound webhook, and Supabase.
- **Send idempotency + delivery worker** ([HT-16](https://resonantiq.atlassian.net/browse/HT-16)) β€” required before `sendReply` sits behind a retrying caller.
- **An Agent inbox UI** over the API (API-first, per the charter).

## Not yet / deferred

- Live Vercel + Supabase deployment β€” deferred to the first deployable milestone; provider adapters are interfaces (inbound + outbound), concrete ones not yet built.
- Agent inbox UI.
- Live Vercel + Supabase deployment β€” deferred to the first deployable milestone; the inbound and outbound provider seams exist, concrete adapters are being built now (outbound first).
- A customer-side / self-service API (a separate future surface, designed native when there are customers to serve).
- Marketplace (paid modules, license keys, module registry).

---

_Last updated: 2026-07-10_
_Last updated: 2026-07-11_
Loading