Screen: Inbound
web/inbound.html. Every email and meeting SABLE has ingested, and the place to give a project to
records SABLE could not place itself.
Since 19 Oct 2026 the same queue is the Filing tab in Approvals, and Inbound is no longer on the
main menu; this page still works at its address. Both pages mount one implementation,
web/inbound-queue.js, so a change to filing lands in both places.
Inbound is admin only, with one exception: a lead may move a meeting or email that is already filed
to a project. That is done from Move… on the project page (feedback #90), which calls this route.
Leads cannot file unfiled items, dismiss, restore, or set a routing rule (remember_domain). A
lead's other requests are refused before any data is read, and an item a lead may not move answers
404, the same as a missing id. Leads already see every project in the organization, so a move
exposes nothing new to them.
Why this screen has to exist
An email SABLE cannot attribute to a project is stored, but not indexed — it won't appear in search, on a project page, or in that project's living note. A project page can't show it either, because it has no project. Without this screen, those emails are invisible: ingested, and effectively unreadable. Assigning one here is what actually makes it searchable.
All, and Needs a Project
"All" shows every ingested email, so you can confirm a message arrived regardless of how it was classified. "Needs a project" shows only the ones waiting on a decision. Every row shows the subject, arrival time, from/to, and whether it carried an attachment — never the body. Deciding where a thread belongs is treated as a different action from reading it.
Why an email couldn't be placed
| Reason | Meaning |
|---|---|
| No known contact or domain | Often a roster gap, not a stray email |
| One likely project | SABLE recognizes the sender's domain or a known contact; confirm to assign |
| Several projects match | Two clients on one thread — a judgment call only a person can make; SABLE reports both rather than guessing |
| Projects share an email tag | Two live projects carry a tag the subject contains; remove the tag from one of them |
Email tags: filing by subject line
Admins can give a project email tags under Manage project → Email tags: a
comma-separated list of words or acronyms, such as BGM, Boston Globe (decision: Kirk, 20 Oct
2026). When an email subject or meeting title contains one of them as a whole word or phrase, SABLE
files the item under that project. Tags are saved lower-case and deduplicated. Generic words such
as re, fwd, meeting, sync, weekly, status, follow up, q4 plan, or Indigo Trigger
are refused.
Each project can have up to 20 tags of 3–40 characters. If another live project already uses a
tag, SABLE saves it and shows a warning. Like remembering a domain on this screen, tags are a
routing rule, so only admins can view or edit them. Leads do not see the control, and neither does an
admin using View as with a lead. A client domain or known contact on a message still takes priority
over a tag.
How a tag is weighed against other evidence:
- A known contact that identifies one live project always wins, and a tag never overrides it.
- If a client domain or contact identifies the client but not the project (for example, two live BGM projects), a tag that matches exactly one of that client's projects decides. A tag belonging to a different client is ignored, and the domain decides. If the domains or contacts identify two clients, a tag does not choose between them.
- If the message has no recognized contact or client domain, a tag that matches exactly one live project decides. This includes internal-only emails. Tags take priority over project codes and SABLE's reading of the content. When a teammate forwards an email to the SABLE inbox, SABLE still checks the original To and Cc lines. A client domain found there wins over the tag.
- If the tags match two or more live projects, SABLE does not choose. The item goes to Needs a project with those projects as suggestions.
Matching ignores Re:, Fwd:, and [EXTERNAL] prefixes. "BGMP" does not match the tag bgm.
Completed projects' tags are ignored. The item records how it was filed (email_tag and the
matching tag), so the row shows the reason, for example "Filed by a project email tag in the subject
— email_tag:bgm". Tags apply when an item is ingested. They do not refile items that are already
filed, and suggestions for items already in Needs a project do not include tags yet.
The same rule applies to meeting titles from Fathom, Granola, and Google Calendar. They use the
same resolver (supabase/functions/_shared/project_resolver.js). On Calendar, a tag by itself gives
medium confidence, so it does not make an event available to the agent. Gmail sync, which is not in
production, still uses its own matcher.
Assigning, and teaching SABLE for next time
Choose a project and press Assign. Suggestions are marked, but you can pick any project — you often know something the roster doesn't. Checking "also remember this domain" teaches SABLE to attribute future email from that company automatically; it's only offered when exactly one outside company appears on the message, because with two, remembering the wrong one would quietly misfile everything that follows. The assignment saves immediately, but indexing happens on the next scheduled sync, within the hour — not instantly.
Dismiss from triage, or remove from a project
On an unfiled meeting or email, Dismiss from triage takes it off the working triage list while retaining the record and its extracted information. Restore puts it back.
On a filed meeting or email, the same action is labelled Remove from project, because that is what it does: it takes the item off its project, along with its extracted work and its lines in the project's note, and moves it to Dismissed. SABLE counts what will come off and asks you to confirm first. Restore then returns it to Needs a project, not to the project it was on — file it again to put it back.
Either way, dismissed records remain under Dismissed with the actor, time and structured reason,
and nothing but the project note lines is deleted. The stored action and audit codes still use
reject/restore for compatibility, but the interface says what the action actually does.
Delete a meeting from SABLE
Organization admins can choose Delete from SABLE from any meeting row or from the shared Meeting
Brief. The dialog loads meeting identity and affected-record counts, not the transcript. It names
linked tasks, decisions and promises whose text, people and URLs will be redacted. It also identifies
mixed-source deliverables whose entire active, client and evidence-pack draft bodies will be withdrawn;
their unrelated evidence links and relationships remain so the drafts can be rebuilt from remaining
sources. These impacts require a separate acknowledgment before the structured reason and exact
DELETE confirmation can unlock. If the meeting or dependencies changed after preview, the admin must
review a fresh count before trying again.
On success, the meeting and traceable SABLE copies are removed atomically and known provider aliases are suppressed from future import. The provider original in Fathom or Granola is unchanged. Already sent or downloaded copies, retained backups, historical CMA memory/session copies and older chat answers without source tracking are outside this operation. A meeting with unsupported linked storage cannot be deleted through this action. Permanent deletion currently applies to meetings, not emails. The Deletion history action shows minimal content-free receipts: receipt ID, actor, time, structured reason, counts and completed state. Deleted content has no Restore action.
Who can use it
Admins and leads can triage. Permanent meeting deletion and deletion history are organization-admin only and are checked again on the server and in the database.
Where the code lives
web/inbound.htmlweb/meeting-deletion.js,web/api/meeting-delete.jsweb/api/inbound-emails.js,_inbound_email.js— see the Inbound Email APIsupabase/functions/email-webhook/,email-sync