email-manual-actions-and-storage · v1.0.0 · 2026-09-07 · sha256 c59ca904a254ff23

email-manual-actions-and-storage v1.0.0A

Immutable. This exact content is served forever at /api/v1/blob/c59ca904a254ff23.

---
name: email-manual-actions-and-storage
description: >
  Focused Email reference for action inputs, folders, check filters, delivery
  side effects, self-send/time capsules, contacts, and mailbox persistence.
  Read after email-manual when an operation needs more than the first-call map.
version: 1.0.0
tags: [lingtai, email, actions, mailbox, storage]
last_changed_at: "2026-09-06T00:00:00Z"
related_files:
- src/lingtai/tools/email/manual/SKILL.md
- src/lingtai/tools/email/_family_schema.py
- src/lingtai/tools/email/manager.py
- src/lingtai/tools/email/primitives.py
- src/lingtai/tools/email/settings.py
- src/lingtai/tools/email/CONTRACT.md
maintenance: |
  Tracks Email action behavior, input details, mailbox layout, and persistence guidance; update when action contracts or storage ownership changes.
---

# Email actions and storage

The root manual is the first-call router. This page supplies action-level detail;
input keys remain closed to the action that owns them. Omitted optional values
use the action defaults. An explicit `null` is treated as omitted at the family
boundary.

## Sending

```python
email(action="send", input={
    "address": "peer", "subject": "status", "message": "ready",
    "cc": ["human"], "bcc": [], "attachments": [], "delay": 0,
}, reasoning="report status")
```

`address` is a bare peer name (or an authorized absolute path in `abs` mode).
`cc` is visible to recipients; `bcc` is stored in the sender's copy but hidden
from recipients. `attachments` contains source file paths. Each recipient
adapter validates every path before creating that recipient's inbox entry; a
missing or non-file path leaves no partial recipient inbox entry. Accepted files
are copied into the message's recipient-local `attachments/` directory before
atomic publication, and `message.json` records those snapshot paths. Duplicate
basenames receive `-1`, `-2`, ... suffixes before the extension. Email enforces
no attachment-size or source-root-containment limit, so callers must select only
explicitly authorized files. This is separate from `message`, which is capped at
50,000 Unicode characters at send time; oversize bodies are rejected with the
limit and actual size rather than truncated.

A send writes sender-side outbox/sent state synchronously, then starts one daemon
mailman thread per recipient. A successful `{status: "sent"}` receipt means the
attempt was scheduled, not necessarily delivered. `delay` is a non-negative
number of seconds before the one delivery attempt; the outbox record exists while
the thread waits. See [Notifications and delivery](../notifications-and-delivery/SKILL.md)
for liveness and bounce behavior. Identical consecutive sends are guarded as a
loop; the installed pass count is in [Settings reference](../settings-reference/SKILL.md).

## Listing and filtering

`check` lists newest-first by default from `inbox`; `folder` can be `inbox`,
`sent`, or `archive`, and `n` defaults to 10. Its optional `filter` is owned by
`check` alone:

```python
email(action="check", input={
    "folder": "inbox", "n": 20,
    "filter": {
        "sort": "newest", "from": "peer", "subject": "status",
        "contains": "blocker", "after": "2026-04-01T00:00:00Z",
        "before": "2026-05-01T00:00:00Z", "unread_only": True,
        "has_attachments": False, "truncate": 500,
    },
}, reasoning="find unread status mail")
```

`sort` is `newest` (default) or `oldest`; `from`, `subject`, and `contains`
are case-insensitive substring filters; `after`/`before` accept ISO 8601
timestamps; the two boolean filters select unread or attachment-bearing mail.
`truncate` controls preview characters (default 500); `0` requests the full
body. A check result can be trimmed by Email's token budget and reports that
fact. Prefer filtering in the call rather than retrieving a large mailbox and
post-filtering mentally.

`search` takes a regular-expression `query` and optional `folder`; it searches
sender, subject, and body and rejects invalid regexes. It does not accept
`filter` or `n`.

## Reading, mutating, and contacts

- `read` takes a list of mailbox IDs, returns source records (including
  attachments), and marks inbox IDs read.
- `dismiss` takes a list of IDs and marks handled inbox mail read without
  returning bodies. Prefer it after handling a body already in persistent
  notification context.
- `reply` and `reply_all` take one ID and a message and route from the original
  mail. Recipient selection, subject derivation, and the absence of persistent
  thread metadata are owned by [Addressing and replies](../addressing-and-replies/SKILL.md#same-channel-reply-discipline).
  Both obey the root manual's same-channel rule.
- `archive` moves inbox IDs to `mailbox/archive`; it removes them from the read
  set as part of the move. `delete` permanently removes IDs from inbox or
  archive and refuses `sent`.
- `contacts` lists the private address book. `add_contact` upserts by address;
  `remove_contact` deletes by address; `edit_contact` changes supplied name or
  note fields. Contact writes use a temporary file and atomic replacement.

All IDs must come from the current agent's own notification or mailbox result.
Stale IDs produce a not-found hint; an ID from another working directory has no
meaning here.

## Self-send and time capsules

Sending to your own bare address creates an ordinary unread inbox message that
survives molt, can be searched later, and remains in the unread lane until
`dismiss`, `read`, `archive`, or `delete`. Add `delay` to make a one-shot time
capsule: the outbox is written immediately, and the daemon mailman thread waits
until the deadline. A delayed self-send is a future nudge, not delayed tool
execution. Recurring sends are not supported; use a host scheduler through
`shell-manual` for recurring work.

## Mailbox layout

Paths are relative to the agent working directory:

```text
mailbox/inbox/<id>/message.json       received mail
mailbox/sent/<id>/message.json        sent copy (one record per send call)
mailbox/archive/<id>/message.json     archived inbox mail
mailbox/outbox/<id>/message.json      pending/delayed send
mailbox/read.json                     read-ID set
mailbox/contacts.json                 private contact book
.notification/email.json              producer-owned unread mirror
```

Message JSON uses UTF-8. Mailbox message files are direct writes; contact writes
are the atomic exception. BCC data is not exposed to recipients. The unread
mirror is refreshed by every read-state mutation; its payload and delivery
semantics are in the notifications reference.