# Explicit staff email dispatch

Phase 3 milestone 18, built locally on **2026-10-17** during **Phase 6 pilot preparation**. Web, iOS and Android now expose a separate dispatch action for an approved email. **Sending remains disabled in source defaults and staging configuration.** No provider was activated or contacted during validation. There is no queue, scheduled sender, automatic retry or automatic recovery dispatch.

## Staff workflow

1. Save a reply, prepare its frozen snapshot and obtain the existing separate-administrator approval. Approval never sends or creates an attempt.
2. Open **Send approved email** in the conversation. Readiness checks the current approval, prior attempts and private SMTP authorization without contacting the provider. It is an advisory check at the displayed time, not a reservation or a connection test.
3. If ready, inspect the exact sender, recipient, subject and complete plain-text body. Confirm that message, then press **Send approved email once**. Refreshing readiness, changing the context or encountering an error clears confirmation.
4. Inspect the resulting attempt history. Server acceptance means **delivery unverified**. A lost response does not prove the server rejected the message. Refresh history before any further action; never resend to resolve uncertainty.

Viewers cannot dispatch. Active traders and administrators use the same server-enforced review boundary. Approval remains invalidated by changes to the saved draft, conversation, account or approving authority under the existing rules. Clients display business content literally; email text is never executable UI or management instruction.

## API and duplicate protection

`GET /v1/inbox/{identity}/outbound/dispatch` returns `enabled`, `ready`, `network_checked: false`, check time, redacted reasons and, when available, the approved snapshot/version/digest. Disabled and viewer checks do not open private SMTP files. A ready response does not establish credential validity, sender ownership or delivery.

`POST` to the same path requires a positive integer `approved_version`, the exact sealed MIME `sha256`, UUID `request_key`, and `confirmed: true`. Extra fields and malformed confirmations are rejected. The web proxy retains its same-origin protection. Only an authenticated active writer may invoke the action.

The service locks and rechecks current authority/context, verifies the sealed approval/history/index and exact digest, validates private authorization, then durably claims one attempt. Private configuration is prepared before claim; it performs no network I/O. The existing handoff fence commits before SMTP and rechecks current authority immediately before DATA. Claims, handoff transitions and outcomes retain their existing audit/evidence rules.

An identical per-actor request returns the saved attempt without calling transport again, even if its state is still claimed/handing off or the operator has since disabled new dispatch. A changed payload with the same key conflicts. Another key cannot reuse an approval. An unresolved earlier attempt prevents a new attempt. Clients retain a key for the same request in memory but never automatically retry POST; server one-attempt protection survives navigation or loss of that key.

Dispatch is synchronous. A failed HTTP/completion write can leave an attempt unfinished even after provider acceptance; it must not be resumed by a repeated HTTP request. The native JSON client and web proxy allow up to 60 seconds for this action. SMTP has its separate 20-second network budget; this is not a hard database-lock/process deadline. Hosted concurrency, latency and process shutdown still require acceptance.

## Private operator controls

`PB_OUTBOUND_DISPATCH_ENABLED=false` is the default. The prepared staging Compose file explicitly sets it to `"false"` rather than inheriting an activation value. Settings are loaded on process startup. No admin UI edits this flag or credentials.

Later activation requires separately authorized provider testing, the operator deliberately changing that runtime setting, and both owner-only private files described in [SMTP-TRANSPORT.md](SMTP-TRANSPORT.md): `PB_OUTBOUND_CONFIG_FILE` and `PB_OUTBOUND_POLICY_FILE`. The policy must authorize the exact provider/sender/username and bounded recipient list with an expiry within 24 hours. No private mount, credential or enabled policy was created here. Live testing remains deferred at the user's request.

The route checks its switch before preparing and invoking transport and at the final body gate. The transport re-reads exact private-file bytes before network I/O, authentication and DATA. Revoking/changing the files prevents the next gated action; it cannot recall credentials or bytes already transmitted. Stop/restart operations can interrupt in-flight work and must be followed by history inspection, not replay.

## Recovery and limits

Use [attempt history and administrator review](EMAIL-ATTEMPTS.md) for expired/interrupted claims or uncertainty. Recovery requires the existing explicit confirmation that the dispatcher has stopped; it never sends. Notes and resolutions retain evidence and do not prove delivery. Investigate provider evidence before considering a newly prepared and separately approved revision.

`sending_enabled` in review/history metadata reflects only the runtime switch. Message readiness and the final dispatch gate remain authoritative. Review `delivery_status` is `not_sent` before any attempt and conservatively `unverified` after one exists, including blocked or rejected attempts; inspect protocol history for details.

This pilot supports one approved correspondent and plain text only. Attachments, CC/BCC, outbound WhatsApp, background dispatch and provider failover remain outside this workflow. Incoming structured delivery/bounce evidence is now handled separately in [DELIVERY-REPORTS.md](DELIVERY-REPORTS.md), always as unverified claims. A successful synthetic run does not validate production egress, DNS, TLS, authentication, mailbox activation, deliverability or device behavior.

## Local evidence

API tests cover strict input/roles, disabled no-file/no-network behavior, stale approval/policy/digest, acceptance/rejection/uncertainty with synthetic TLS peers, concurrent commands, interrupted claim, failed result persistence and repeated requests without replay. PostgreSQL checks use disposable schemas. Three browser scenarios exercise the actual API/ledger with a synthetic no-network transport, explicit confirmation, literal content, history refresh, lost responses, failed readiness, viewer and cross-origin rejection. Native timeout coverage confirms one aborted request without replay; unsigned exports are not signed/device tests. Exact results and release packages are recorded in [VALIDATION.md](VALIDATION.md).
