Skip to main content

Inbound Email API

/api/inbound-emails backs the Inbound Email screen — every email SABLE has ingested, with the project it landed on or the candidates for one.

GET /api/inbound-emails?scope=all|unassigned|rejected​

all and unassigned both hold back rejected rows; pass scope=rejected to see those instead. Admin/lead only.

POST /api/inbound-emails​

{ "id": "email-source-uuid", "project_id": "project-uuid", "remember_domain": true }
{ "id": "email-source-uuid", "action": "reject", "reason": "wrong client" }
{ "id": "email-source-uuid", "action": "restore" }

Why this route never returns the email body​

Email bodies are client correspondence; this route deliberately returns metadata and the subject line only, never the body. Reading a thread's content is treated as a different permission from triaging where it belongs — the account page already shows attributed mail's content to the people who actually own the project, so the triage screen doesn't need to duplicate that exposure.

Assignment is not cosmetic​

Setting project_id here does real work, not just a relabel. A source with no project_id skips knowledge indexing and the living project-note entry at ingest time. fathom-sync's bounded link-repair sweep (see fathom-sync) completes exactly those side effects once project_id becomes non-null — so an assignment made through this route is what makes the email searchable and puts it into the project's note, on the next scheduled poll, with no separate re-extraction step.

remember_domain​

Setting this alongside an assignment teaches the router: future email from the same sending domain gets a stronger signal toward the same project, without requiring the same manual assignment every time.

Where the code lives​

  • web/api/inbound-emails.js, _inbound_email.js
  • supabase/functions/email-webhook/index.ts, email-sync
  • web/inbound.html