From bd040a5e21bcd7129a35ec4a6dfe94dfe2923cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 23:22:57 +0000 Subject: [PATCH] carry the email-db skill in this repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It lived in capabilities/skills/email-db, hand-written into the owner's store rather than published by anything — the plugin shipped no skills directory at all, so that file existed in exactly one place and nothing tied it to the plugin it documents. The platform now reads a plugin's skills from the plugin's own directory, so this is where it belongs: it moves with the plugin, updates with it, and disappears when the plugin is disabled instead of outliving it in somebody's fork. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- skills/email-db/SKILL.md | 216 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 skills/email-db/SKILL.md diff --git a/skills/email-db/SKILL.md b/skills/email-db/SKILL.md new file mode 100644 index 0000000..eb2e2da --- /dev/null +++ b/skills/email-db/SKILL.md @@ -0,0 +1,216 @@ +--- +name: Email database +description: Use when querying the user's synced email — searching messages, senders, domains, attachments or counts. Carries the SQLite schema and the queries worth knowing, so you do not have to rediscover them. +version: 1 +--- + +**There is one database per ACCOUNT, and the owner may have several.** + +``` +$OFFICER_EMAIL_ACCOUNTS_DIR/ one directory per connected address + andre.padez@gmail.com/emails.db + someone.else@work.com/emails.db + attachment_cache/ NOT an account — skip it +``` + +`$OFFICER_EMAIL_DB` points at exactly **one** of them: the most recently synced. That is the right guess +for "what did X say yesterday" and the wrong one for anything that means *all* my mail — you will search +one mailbox and report as though you searched every one. + +So: for a question about a specific account, or a quick lookup, use `$OFFICER_EMAIL_DB`. For anything +that spans the mailbox, **list `$OFFICER_EMAIL_ACCOUNTS_DIR` first** and query each `emails.db` you find, +or `ATTACH` them into one connection. Say which accounts you actually covered. + +Query with the `email_db` tool, or with `sqlite3` directly. + +**Two things measured on a real 19,000-message database (2026-08-20), because the schema alone misleads:** + +- **`read` is almost never populated.** 93 rows of 19,097 had it set. The documented unread query + (`WHERE read = 0`) therefore returns essentially the whole mailbox. Do not report an unread count from + it without saying what it actually measures. +- **`labels` is a comma-separated string, not a set.** Real values include `updates,inbox` and + `important,updates,inbox`, so `labels = 'inbox'` misses most of the inbox. Use `LIKE '%inbox%'`, and + mind that `promotions,inbox` matches too. + +There is a full-text index (`emails_fts`) over the same rows — prefer it to `LIKE '%...%'` on `text_body` +for anything word-shaped, since the table holds ~0.5 GB of bodies. + +# Email Database Schema + +The email database is a SQLite file containing synced emails from the user's connected accounts (Gmail, IMAP, etc.). + +--- + +## Tables + +### emails + +Primary table. One row per email message. + +| Column | Type | Description | +|--------|------|-------------| +| `id` | TEXT PK | Unique message ID (e.g. Gmail message ID) | +| `integration` | TEXT | Email provider (`gmail`, `imap`) | +| `email_account` | TEXT | Account address this email belongs to | +| `from_name` | TEXT | Sender display name | +| `from_address` | TEXT | Sender email address | +| `from_domain` | TEXT | Sender domain (extracted from address) | +| `to_address` | TEXT | Recipient address | +| `cc` | TEXT | CC recipients | +| `subject` | TEXT | Email subject line | +| `date` | TEXT | ISO 8601 date string (`YYYY-MM-DD HH:MM:SS`) | +| `snippet` | TEXT | Short preview of body text | +| `html` | TEXT | Full HTML body (may be large) | +| `text_body` | TEXT | Plain text body | +| `attachment_count` | INTEGER | Number of attachments (0 if none) | +| `read` | INTEGER | Read status (0 = unread, 1 = read) | +| `deleted` | INTEGER | Soft-delete flag (0 = active, 1 = deleted) | +| `labels` | TEXT | Comma-separated labels (e.g. `INBOX,IMPORTANT`) | + +**Indexes**: `date`, `from_domain`, `from_address`, `integration`, `email_account`, `labels`. + +### attachments + +One row per email attachment. + +| Column | Type | Description | +|--------|------|-------------| +| `id` | INTEGER PK | Auto-increment ID | +| `email_id` | TEXT FK | References `emails(id)`, cascades on delete | +| `idx` | INTEGER | Attachment index within the email | +| `filename` | TEXT | Original filename | +| `size` | INTEGER | Size in bytes | +| `content_type` | TEXT | MIME type (e.g. `application/pdf`, `image/png`) | +| `content` | TEXT | Base64-encoded file content | + +### sync_meta + +Internal sync state. Generally not useful for querying. + +| Column | Type | Description | +|--------|------|-------------| +| `key` | TEXT PK | Metadata key | +| `value` | TEXT | Metadata value | + +--- + +## Common Queries + +### Recent emails +```sql +SELECT id, date, from_name, from_address, subject, snippet +FROM emails +WHERE deleted = 0 +ORDER BY date DESC +LIMIT 20; +``` + +### Search by subject or body +```sql +SELECT id, date, from_address, subject +FROM emails +WHERE deleted = 0 + AND (subject LIKE '%keyword%' OR text_body LIKE '%keyword%') +ORDER BY date DESC; +``` + +### Emails from a specific sender +```sql +SELECT id, date, subject, snippet +FROM emails +WHERE from_address = 'sender@example.com' AND deleted = 0 +ORDER BY date DESC; +``` + +### Emails from a domain +```sql +SELECT id, date, from_address, subject +FROM emails +WHERE from_domain = 'example.com' AND deleted = 0 +ORDER BY date DESC; +``` + +### Top senders +```sql +SELECT from_address, from_name, count(*) AS total +FROM emails +WHERE deleted = 0 +GROUP BY from_address +ORDER BY total DESC +LIMIT 20; +``` + +### Top domains +```sql +SELECT from_domain, count(*) AS total +FROM emails +WHERE deleted = 0 +GROUP BY from_domain +ORDER BY total DESC +LIMIT 20; +``` + +### Unread count +```sql +SELECT count(*) AS unread FROM emails WHERE read = 0 AND deleted = 0; +``` + +### Emails with attachments +```sql +SELECT e.id, e.date, e.from_address, e.subject, e.attachment_count +FROM emails e +WHERE e.attachment_count > 0 AND e.deleted = 0 +ORDER BY e.date DESC; +``` + +### List attachments for an email +```sql +SELECT a.filename, a.size, a.content_type +FROM attachments a +WHERE a.email_id = 'MESSAGE_ID'; +``` + +### Find attachments by type +```sql +SELECT e.id, e.date, e.subject, a.filename, a.size, a.content_type +FROM attachments a +JOIN emails e ON e.id = a.email_id +WHERE a.content_type LIKE 'application/pdf%' +ORDER BY e.date DESC; +``` + +### Email volume by month +```sql +SELECT strftime('%Y-%m', date) AS month, count(*) AS total +FROM emails +WHERE deleted = 0 +GROUP BY month +ORDER BY month DESC; +``` + +### Emails by label +```sql +SELECT id, date, from_address, subject +FROM emails +WHERE labels LIKE '%INBOX%' AND deleted = 0 +ORDER BY date DESC +LIMIT 20; +``` + +### Full email content +```sql +SELECT id, date, from_name, from_address, to_address, cc, subject, text_body, html, labels +FROM emails +WHERE id = 'MESSAGE_ID'; +``` + +--- + +## Notes + +- **Soft deletes**: Always filter `deleted = 0` unless you want deleted emails. +- **Labels**: Stored as comma-separated text. Use `LIKE '%LABEL%'` to filter. +- **Date sorting**: The `date` column is TEXT in ISO format, so lexicographic sorting works correctly. +- **Large content**: `html` and `text_body` can be very large. Use `snippet` for previews, only select full body when needed. +- **Attachment content**: The `content` column in `attachments` is base64-encoded. It can be very large — avoid selecting it unless you need the actual file data. +- **SQL reference**: For full SQLite syntax, functions, and operators, refer to the **sqlite** tool documentation.