# Mail — what you can do

<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run pnpm skills:sync. -->

<!-- GENERATED by ops/tools/skill-sync — do not edit by hand. Run `pnpm skills:sync`. -->

# Mail — what you can do

`/admin/d/mail` · pillar `protocols`

An install email address that runs callables: subject names one, body supplies its props, reply returns the result.

## Fetch this, never remember it

These change per install and per release. Call for them - a list written here would be a future lie.

| What | Call | Why |
|---|---|---|
| The address, the allow list and whether secrets are encrypted at rest | `GET /api/mail/config` | `GET /api/mail/config` | Tells you the inbound domain and whether this install seals secrets. `sealing: false` means the address token is stored in the clear. |
| The mailbox itself, with its token masked | `GET /api/mail/mailboxes` | `GET /api/mail/mailboxes` | One per install. The address is shown with the token masked to its last four; POST /api/mail/mailboxes/:id/reveal returns it whole. |
| What a subject line is allowed to run | `GET /api/mail/bindings` | `GET /api/mail/bindings` | Each binding is one subject to one callable, plus the argMap that decides which message keys reach it. |
| Every message handled, and every one refused | `GET /api/mail/deliveries` | `GET /api/mail/deliveries` | The door answers 200 to everything so it cannot be probed, so this log is the ONLY place a refusal is visible. `phase` names the gate it stopped at. |

## How to actually do things here

Written by hand, because the order and the traps are the part no file can derive.

### Let a colleague email a workflow and get the result back

1. Create the address on the Address tab, or POST /api/mail/mailboxes with a localPart.
2. Add their address to the allow list — an EMPTY allow list accepts nobody, which is the safe default and the surprising one.
3. Add a binding whose subject is the reference they will type, bound to the workflow, with an argMap from `args.<key>` or `text` onto its declared inputs.
4. Send them the full address including the +token: it is the shared secret, and From: alone is forgeable.

**Trap:** The argMap is a WHITELIST — an input you did not map never reaches the callable, whatever the sender puts in the body. A binding on a denied action (http-request, fs-*, unpinned crud-config) is refused when you save it, not when mail arrives.

### React to mail without writing a binding

1. Build a rule on the `email-received` trigger; its `mailbox` prop is the address to watch.
2. The payload carries { from, to, subject, snippet, messageId, ts }, so `{{from}}` and `{{subject}}` resolve in any action's props.

**Trap:** Two delivery paths feed that trigger and they are not equally trustworthy: the mail door has a real SPF/DKIM verdict, the IMAP listener has none, so a rule that acts on `{{from}}` from a polled mailbox is acting on a claim.

### Work out why mail is not arriving

1. Read GET /api/mail/deliveries and look at `phase`.
2. `sig` means the provider signature failed — the MAIL_INGRESS_SECRET variable and the Worker secret disagree.
3. `verdict` means SPF and DKIM both failed: the sender is not who the header claims.
4. `sender` means they are not on the allow list. `secret` means the address they used had the wrong token.
5. No rows at all means nothing reached the API: check the MX record and the Worker.

**Trap:** An unknown recipient writes NO row on purpose — an anonymous sender must not be able to fill the log — so silence can mean the local part is wrong.

## Tabs

Every one is a real URL. A tab with its own section below carries a real job.

| Tab | Path | Group | What it is for |
|---|---|---|---|
| `address` | - | The address | Creates the address, sets the allow and deny lists, and rotates the token. Warns when this install stores secrets unencrypted. |
| `bindings` | - | The address | Maps a subject line to a callable and its message body to that callable's declared inputs. |
| `deliveries` | - | The address | Shows what the door handled and what it refused, with the gate each refusal stopped at. |

## Tabs in focus

### `address` - Address

Creates the address, sets the allow and deny lists, and rotates the token. Warns when this install stores secrets unencrypted.

**Read with:** `GET /api/mail/config` | `GET /api/mail/mailboxes`

**Write with:** `POST /api/mail/mailboxes` | `PATCH /api/mail/mailboxes/:id` | `POST /api/mail/mailboxes/:id/rotate`

### `bindings` - Bindings

Maps a subject line to a callable and its message body to that callable's declared inputs.

**Read with:** `GET /api/mail/bindings` | `GET /api/mail/bindable`

**Write with:** `POST /api/mail/bindings` | `PATCH /api/mail/bindings/:id` | `DELETE /api/mail/bindings/:id`

### `deliveries` - Deliveries

Shows what the door handled and what it refused, with the gate each refusal stopped at.

**Read with:** `GET /api/mail/deliveries`

## API

Mounted prefixes that serve this domain: `/api/mail` · `/api/mail/inbound`

The full route table is `leumas-capabilities/reference/api-routes.md`.



---
Source: .claude/skills/leumas-studio/reference/domains/mail.md
Canonical: https://docs.leumas.tech/p/skills/leumas-studio/domains/mail
