pastilhasandClaude Opus 5 c538086f2b the chat says which mailbox it is in, and reads a brief instead of a prefix
the same arrangement proxy-manager got, with one difference that turned out to
matter: an email chat does not run in the plugin's chats directory, it runs in
the ACCOUNT's, beside emails.db.

three things.

the panel declares its chat context. it declared none, and it did not need to
while email was part of the platform — ChatPanelWrapper recognised the screen by
`workspace.id === 'email'` and supplied it. a plugin's workspace key is
`screens/plugin/email`, so that id became `plugin/email` on extraction day and
the branch silently stopped matching. every email chat since has run somewhere
with no mailbox anywhere near it. declaring it here is what that branch's own
comment asked for, and it carries the mailbox too, which a hardcoded screen id
never could — with two accounts the chat follows the picker.

the prompt prefix is now chat/CLAUDE.md. a prefix rides every message: paid per
turn forever, invisible as a design artefact because it lives in a typescript
constant, and visible in the one place it should not be. the file costs nothing
per turn and the person it is about can open it. it says more than the prefix
did, because it can afford to: answer from the local db and not from gmail, the
screen already shows what you would paste back, and a bulk delete over 19k rows
is not reversible from here.

the panel gets a header, so the conversation picker is scoped to this mailbox's
directory rather than absent. panelId is a prop now instead of the hardcoded
"email-chat" — two of these could never have held different conversations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-26 18:42:59 +00:00

Email

IMAP and Gmail accounts, synced into a per-account SQLite store on your own machine and searchable offline.

  • Your mail stays local. Each account gets its own emails.db under the platform's data directory, with an FTS5 index and Gmail-style search operators. Nothing is sent anywhere to be indexed.
  • IMAP or Gmail. Generic IMAP over imapflow, or Gmail's history API where an account is connected through the platform's Google integration.
  • Real-time. IMAP IDLE watchers push new mail as it lands, with a slow cron poll as a backstop.
  • Composing and sending, with attachments, over the account's own SMTP.

Status — read this before installing

This plugin has never been run. It was extracted from the Officer platform on 2026-08-17, where it had been switched off since 2026-08-13 — its database table was commented out of the schema and therefore never created, so nothing has exercised sync, sending or the UI since. The code is believed good (the sidecar starts cleanly and typechecks) but that is not the same as working.

Treat the first install as the first test. docs/email-plugin-extraction.md in the platform repository records what was moved and what was decided.

Known gaps

  • Credentials are stored unencrypted. email_accounts.credentials is a jsonb blob holding the IMAP app password in the clear. The platform has a secret store with per-purpose keys and this plugin does not use it yet. Deliberately left alone during the extraction so that a first-install failure has exactly one possible cause; it is the first thing to fix afterwards.
  • The email_db agent tool is not part of this plugin. It is resolved at runtime from PI_TOOLS_DIRS and the platform points it at the account's SQLite store. The email chat panel's prompt assumes it exists.
  • Account management placement is unsettled — see the extraction doc.
S
Description
IMAP and Gmail accounts, synced locally and searchable offline
Readme
1.8 MiB
Languages
TypeScript 100%