# Application email handoff and isolated rehearsal

Implemented locally on **2026-10-17**, closing Phase 3 milestone 16 while the overall project remains in **Phase 6 pilot preparation**. This connects sealed approvals to the application's actual `email_attempts` and `email_attempt_events` tables. **Live sending remains disabled by default.** This milestone added the internal handoff core. Subsequent private transport and explicit staff dispatch are documented in SMTP-TRANSPORT.md and EMAIL-DISPATCH.md. Preparing or approving a message still creates no attempt; the separate confirmed dispatch action claims it.

## What is connected

The internal `phonebuzz.email_handoff.Handoff` service uses the same conversation/account locking order as approval and administrator recovery. A claim requires an active writer session, current draft/conversation and staff access versions, separate-administrator approval, an unused approval identity and no unfinished/uncertain attempt on that outbound record. An identical claim retry returns the existing attempt; a conflicting key or reused approval is rejected.

Before claiming, before handoff and at the final body gate, the service verifies agreement between current approval, immutable approval history, outgoing identity index and frozen snapshot. It verifies the final Message-ID, aware Date, threading metadata, byte length and SHA-256, then serializes the approved fields with the retained identity/date to require exact byte equality. It never generates a fresh identity during dispatch. Existing approvals without a sealed final format require a fresh preparation and separate approval.

A durable `handing_off` transition commits before the supplied transport can run. Another dispatcher cannot replay that attempt. The transport must enter the final body gate immediately before transmitting DATA; this rechecks current session, token, deadline and approval context. The gate permits one body exchange. The synthetic adapter uses bounded I/O and retains the database locks through that exchange. This lock/latency strategy still needs production design and load acceptance.

Completion retains accepted, rejected, not-submitted or uncertain protocol evidence. Acceptance is **delivery unverified**. Context changes after an observed acceptance are recorded without erasing the outcome. Exceptions or acceptance reported without entering the gate become uncertain; raw protocol/exception diagnostics are excluded from history. Late completion with an expired or cleared token is rejected.

Recovery and review use the existing administrator-only API and UI, including version checks, notes and explicit worker-stop attestation. Expired untouched claims become blocked; expired handoffs become uncertain. Review preserves the original outcome and sealed snapshot. It never resends or reuses an approval. A new approved revision can be claimed only after the uncertainty is reviewed. An administrator attestation does not itself stop or verify a real worker.

The injected transport is trusted internal code, not a sandbox or a plugin API. The default-disabled staff dispatch route now invokes this service with the private transport. SMTP-TRANSPORT.md describes envelope/TLS/deadline enforcement and private authorization. No real credentials were read or activated during implementation.

## Run the development rehearsal

From the full local `platform` checkout:

```sh
apps/api/.venv/bin/python scripts/check-email-handoff.py
```

This is a development tool, not part of the server-source ZIP. The application module is included in that ZIP but remains inactive. The earlier `check-smtp-handoff.py` prototype remains available for its independent laboratory-table tests.

The new command accepts no host, recipient, database or credential parameters. It starts a clean-environment isolated child, migrates temporary SQLite, refuses populated application tables, creates synthetic staff and prepares/approves messages through the actual API. It uses fixed example.com envelope addresses and a fake SMTP peer over a Unix socket pair. External connection, bind and DNS attempts fail the run. Inherited private configuration is discarded; temporary databases, certificates, synthetic sessions and receipts are removed afterward. This protection is a development harness for trusted source, not an operating-system sandbox for hostile code.

All **13 checkpoints** must pass: four outcomes in STARTTLS and implicit TLS, administrator recovery, uncertainty review, and three real process exits after claim, before peer contact and after fake acceptance. The last case writes independent synthetic peer evidence before abruptly exiting without completing the ledger. Recovery must retain uncertainty and reject late completion/replay. This is a process-interruption rehearsal, not a power-loss test or proof of hosted durability.

Owner-only reports are saved under `.local/email-handoff/`, with fixed labels, pass/fail, schema revision, disabled-sending/delivery flags and cleanup evidence. They omit message bodies, session tokens and provider credentials. Exit 0 means all required evidence passed; exit 1 means failure; unknown arguments return 2. Filenames use UTC. Synthetic acceptance records exist only in disposable databases and must never be copied into real history.

## Validation and remaining work

The new service/rehearsal selection passes **38 tests** locally. A wider PostgreSQL 17.11 selection passes **103 tests**, including **79 private-schema cases**, with fresh migration/schema comparison. Tests cover competing claims/dispatch, missing or altered sealed evidence, mailbox/access/draft changes, final-gate races, lost completion, preserved acceptance after revocation, wrong tokens, early/expired recovery, same-approval refusal, fresh approval after review, populated-database refusal, isolated CLI crashes and hostile inherited settings. Full regression and exact server-candidate checks are recorded in [VALIDATION.md](VALIDATION.md).

No schema change is needed: head remains `31c8b020d6a1`. Preview/development databases, existing accounts/passwords and public PHP files were not changed. No web/native code or contract changed; previous client validation remains dated evidence rather than a new device claim.

[Private provider transport and expiring pilot authorization](SMTP-TRANSPORT.md) are now built with synthetic validation. The explicit operational dispatch integration is now built in EMAIL-DISPATCH.md; controlled provider acceptance remains pending. Live provider tests remain deferred by the owner. Hosted startup/load/recovery, real SMTP interoperability/delivery, signed physical devices and staff acceptance are still open. This milestone does not complete Phase 3 or Phase 6.
