email, extracted from the officer platform

IMAP and Gmail sync into a per-account SQLite store, with the mail read and
composed in the browser.

moved whole from src/servers/sidecar/email (14 files), src/servers/api/email,
officer_db/src/email, and the Screens/Dashboard/Email tree. what changed on the
way out:

- credentials are encrypted. the column was plaintext jsonb holding an IMAP app
  password that the SMTP transport read straight out of the row; it is now
  AES-256-GCM under this plugin's own 'email' secret-store purpose, sealed and
  opened in queries.ts alone so nothing above it can hold ciphertext by
  accident. no plaintext fallback on read - a value that does not verify is an
  error.
- the router derives its prefix from mountPrefix() instead of the literal
  '/api/email', which was only correct for a first-party publisher.
- the frontend became panels.ts + layout.ts. the two panel appTypes that were
  null are now declared, because the platform screen used to override them by
  panel id and a plugin has no such hook.
- account management became a modal. it was a section in the platform's
  Settings, which a plugin cannot contribute to, and a panel does not work
  either: PluginScreen renders the workspace locked, so a panel outside the
  default layout is unreachable. it follows Compose, which had already solved
  this.
- 17 palette classes replaced with semantic tokens.

not verified: this has never run. it was switched off in the platform on
2026-08-13 and its table was never created, so nothing has exercised sync,
sending or the UI since. the first install is the first test.
This commit is contained in:
Claude Opus 5
2026-08-18 00:29:50 +00:00
commit 980a9d3c38
30 changed files with 5616 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# 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.