mailreceipt

A worked ANCC use case — raw evidence to cited artifact to safe action. Attach proof, not vibes.

mailreceipt is a small, deterministic CLI that demonstrates the ANCC discipline on one concrete problem: did this email actually arrive? It is a mirror, not an oracle — it finds the right log line, bounds its meaning honestly, and refuses to assert anything it cannot cite.

The situation

A law firm sent a deadline reminder to a client. Weeks later the client says: we never received it. The matter turns on whether that is true.

The firm runs its own Postfix mail server, so the answer is already written down — somewhere in /var/log/mail.log, among hundreds of thousands of unrelated lines. The traditional move is to ask a mail admin to grep the log and read the SMTP status back by hand. That works, but it produces a sentence in an email, not an artifact — and nothing stops a later reader from doubting it.

The tempting modern move is worse: hand the mailbox to an AI agent and ask it to "look into whether the email was delivered." Now the answer is a fluent paragraph with no cited evidence, produced by a model that read far more than it needed to.

mailreceipt is the third path: a deterministic command that produces a cited receipt.

Step 1 — Drop in the email, point at the log

The operator has the dropped email (a real .eml file, or even a pasted top-of-thread block) and the mail log. One command:

mailreceipt check reminder.eml --log /var/log/mail.log --case CASE-001

mailreceipt extracts the Message-ID and recipients from the email, parses the Postfix log into per-recipient delivery events, and correlates them — by message-id when present, otherwise by recipient and time window.

Step 2 — Read the cited receipt

# Mail Delivery Receipt

**Case:** CASE-001
**Subject:** RE: URGENT: Our ref.: CASE-001
**Overall:** Bounced — hard-rejected, not delivered

| Recipient                | Outcome      | When             | Evidence |
|--------------------------|--------------|------------------|----------|
| jdoe@exampleclient.test  | delivered    | 2026-06-05 15:41 | 250 2.0.0 OK |
| team@exampleclient.test  | bounced      | 2026-06-05 15:09 | 550 5.1.1 User unknown |

## Evidence (verbatim log lines)
- jdoe@... (matched by message_id):
    Jun  5 15:41:55 mail01 postfix/smtp[20460]: 7C2D9E1F02:
    to=<jdoe@exampleclient.test>, ... status=sent (250 2.0.0 OK: queued as D4E5F6)
- team@... (matched by message_id):
    Jun  5 15:09:21 mail01 postfix/smtp[20441]: 7C2D9E1F02:
    to=<team@exampleclient.test>, ... status=bounced
    (... 550 5.1.1 ... Recipient address rejected: User unknown ...)

> A 'delivered' outcome means the remote mail server accepted the message
> (SMTP 2xx) at relay handoff. It does not prove a person read it. This
> receipt reports transport, not attention.

The answer is specific: one recipient was delivered, the other bounced with 550 5.1.1 User unknown. Every outcome carries the exact log line it came from. The disposition is Postfix's, quoted verbatim — there is no model in the loop inventing a verdict.

Step 3 — Attach the artifact

The same command with --format json emits the machine-readable artifact the operator attaches to the matter:

mailreceipt check reminder.eml --log /var/log/mail.log --case CASE-001 --format json > CASE-001.receipt.json
{
  "artifact_type": "mail_delivery_receipt",
  "tool": "mailreceipt",
  "summary": "bounced",
  "result": {
    "message_id": "reminder-001-1509@example-ip.test",
    "recipients": [
      {
        "recipient": "jdoe@exampleclient.test",
        "outcome": "delivered",
        "match_method": "message_id",
        "response": "250 2.0.0 OK: queued as D4E5F6",
        "time": "2026-06-05T15:41:55Z",
        "citation": "Jun  5 15:41:55 mail01 postfix/smtp[20460]: 7C2D9E1F02: to=, ... status=sent (250 2.0.0 OK: queued as D4E5F6)"
      },
      {
        "recipient": "team@exampleclient.test",
        "outcome": "bounced",
        "match_method": "message_id",
        "response": "550 5.1.1 : Recipient address rejected: User unknown",
        "time": "2026-06-05T15:09:21Z",
        "citation": "Jun  5 15:09:21 mail01 postfix/smtp[20441]: 7C2D9E1F02: to=, ... status=bounced (... 550 5.1.1 ... User unknown ...)"
      }
    ]
  }
}

This is the ANCC payload: structured, every fact carrying its citation and match_method, ready for a case-management system, a ticket, or a claim to consume without re-reading the mailbox.

Step 4 — Verify it later

A receipt is only evidence if it cannot be quietly edited. Months later, anyone can re-check it against the source log:

mailreceipt verify CASE-001.receipt.json --log /var/log/mail.log
# OK: all 2 citation(s) present in /var/log/mail.log

Every cited line must still appear verbatim in the log. A fabricated or altered citation fails. The artifact is tamper-evident by construction — not because it is signed, but because it points at evidence that either exists or does not.

What the receipt proves — and what it does not

An honest artifact states its own boundary. mailreceipt reports transport, not attention, and says so on every receipt.

What it proves

What it does not prove

Featured: agent-safe use behind a gate

The mail log is sensitive — it lists who emailed whom. The ANCC story is not "let an agent read the mailbox and summarize." It is: gate what the agent may read, bound what it may claim.

When an agent drives the workflow, mailreceipt runs behind a file-access gate such as Bulwark. The gate controls what the agent may read; mailreceipt bounds what it may claim. The agent never touches the mailbox — only the specific evidence the task needs.

# The agent can touch the mail log ONLY through the gate,
# and its answer is cited by construction.
bulwark run --protect ./mail-evidence -- \
  mailreceipt check reminder.eml --log mail.log --case CASE-123 --format json

The composition reads cleanly:

Bulwark        limits what the agent may read
mailreceipt    defines the cited artifact
agent          operates the workflow, does not invent facts
operator       decides what to attach, send, or escalate

No mailbox-wide rummaging. No uncited summary. No over-permissioned agent. The agent gets the minimum evidence, the gate enforces the boundary, and the output is an artifact another system can safely act on. That is the whole ANCC thesis in one minute: tools should produce cited, verifiable artifacts instead of fluent guesses.

Forward an email, get a receipt back

The same engine runs as a mail filter, so a receipt is one forward away — no command line.

Wire an alias (e.g. receipt@yourfirm.test) to mailreceipt filter. A user forwards a sent email — the original attached — and the filter replies with the cited receipt. Authorization is the whole point of the design:

receipt@yourfirm.test  ──►  mailreceipt filter --envelope-from "$SENDER" --log /var/log/mail.log
                              │
                              ├─ internal, MTA-authenticated sender only
                              ├─ sender shares a configured team with the attachment's From/Sender
                              ├─ the attachment's Message-ID must correlate to a real log line
                              └─ any gate unmet → silent drop, nothing disclosed

The attachment is a selector, never evidence: a forged or edited .eml for a message the server never sent returns not_found and reveals nothing. It is the cited-artifact discipline, delivered over email instead of a terminal.

Beyond check: the full surface

mailreceipt is ANCC-compliant — four commands a person or an agent can discover and compose:

Why this belongs on ANCC

Scope today: Postfix syslog format and .eml input only, for self-hosted or relayed mail. It is a demonstration of the principle, not a commercial product — provider connectors (Gmail, Microsoft 365) and a server mode are possible future directions, not promises.