Files
pastilhasandClaude Opus 5 fda9beb80a the skill's name is an identifier, not a label
lowercase-hyphenated and equal to its directory, which is what both harnesses
require to LOAD it. carrying a human title there meant the skill was listed to an
agent and then failed to open. the prose moves to `title`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 13:11:34 +00:00

218 lines
6.6 KiB
Markdown

---
name: email-db
title: 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.