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 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
6.6 KiB
name, description, version
| name | description | version |
|---|---|---|
| Email database | 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. | 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:
readis 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.labelsis a comma-separated string, not a set. Real values includeupdates,inboxandimportant,updates,inbox, solabels = 'inbox'misses most of the inbox. UseLIKE '%inbox%', and mind thatpromotions,inboxmatches 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
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
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
SELECT id, date, subject, snippet
FROM emails
WHERE from_address = 'sender@example.com' AND deleted = 0
ORDER BY date DESC;
Emails from a domain
SELECT id, date, from_address, subject
FROM emails
WHERE from_domain = 'example.com' AND deleted = 0
ORDER BY date DESC;
Top senders
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
SELECT from_domain, count(*) AS total
FROM emails
WHERE deleted = 0
GROUP BY from_domain
ORDER BY total DESC
LIMIT 20;
Unread count
SELECT count(*) AS unread FROM emails WHERE read = 0 AND deleted = 0;
Emails with attachments
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
SELECT a.filename, a.size, a.content_type
FROM attachments a
WHERE a.email_id = 'MESSAGE_ID';
Find attachments by type
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
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
SELECT id, date, from_address, subject
FROM emails
WHERE labels LIKE '%INBOX%' AND deleted = 0
ORDER BY date DESC
LIMIT 20;
Full email content
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 = 0unless you want deleted emails. - Labels: Stored as comma-separated text. Use
LIKE '%LABEL%'to filter. - Date sorting: The
datecolumn is TEXT in ISO format, so lexicographic sorting works correctly. - Large content:
htmlandtext_bodycan be very large. Usesnippetfor previews, only select full body when needed. - Attachment content: The
contentcolumn inattachmentsis 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.