Ship the railiance01 deploy package (OpenBao/ESO custody, NetworkPolicy, probes), provider failure classification and suppression, T04 unit and live proof, and non-secret NK-WP-0024 hand-back evidence. Workplan finished.
151 lines
6.4 KiB
Markdown
151 lines
6.4 KiB
Markdown
---
|
||
id: EMAIL-WP-0004
|
||
type: workplan
|
||
title: "Provide transactional invitation mail delivery"
|
||
domain: infotech
|
||
repo: email-connect
|
||
status: finished
|
||
owner: codex
|
||
topic_slug: netkingdom
|
||
created: "2026-08-08"
|
||
updated: "2026-08-12"
|
||
depends_on:
|
||
- NK-WP-0024
|
||
state_hub_workstream_id: "a37e5e4d-090a-4a75-88d2-aacd0c0fd235"
|
||
---
|
||
|
||
# EMAIL-WP-0004 - transactional invitation mail delivery
|
||
|
||
Extend email-connect beyond mailbox evidence scanning with a narrow,
|
||
provider-neutral transactional-send service for user-engine invitations and
|
||
verification messages. Email evidence remains non-authoritative.
|
||
|
||
Deferred (not in this workplan): daily send-quota privilege tiers
|
||
(10 / 100 / 1e3 / 1e4 / 1e5 / 1e6 / unlimited) as anti-spam abuse control —
|
||
see `DECISIONS.md` “Defer daily send-quota privilege tiers”.
|
||
|
||
## T01 - Define the send and evidence contract
|
||
|
||
```task
|
||
id: EMAIL-WP-0004-T01
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "ef07126a-3c1b-4a8b-9ec1-252c8844ec49"
|
||
```
|
||
|
||
Define an authenticated HTTP send request derived from the user-engine outbox
|
||
envelope, approved templates, recipient/address validation, tenant/template
|
||
allow-lists, Idempotency-Key, correlation, redaction, and response/error
|
||
semantics. Provider acceptance must not be described as inbox delivery,
|
||
awareness, identity, or authorization.
|
||
|
||
Done when the contract supports invitation-created and invitation-resent
|
||
events without accepting arbitrary sender, template, or message content.
|
||
|
||
Done 2026-08-09: authenticated `/v1/send` accepts only the two invitation
|
||
event types, validates recipient/event/idempotency fields, and renders a fixed
|
||
portal invitation template. Provider acceptance remains explicitly distinct
|
||
from delivery or identity evidence.
|
||
|
||
## T02 - Implement provider-neutral transactional sending
|
||
|
||
```task
|
||
id: EMAIL-WP-0004-T02
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "6a6ae927-4393-480f-a83e-93c9dabe18fe"
|
||
```
|
||
|
||
Implement the HTTP receiver, template rendering, idempotency store, suppression
|
||
checks, bounded provider calls, and provider adapter. Start with the approved
|
||
IONOS SMTP/STARTTLS lane while keeping provider credentials outside requests,
|
||
logs, Git, and database evidence.
|
||
|
||
Done when repeated event IDs send at most one message and failures return
|
||
stable retryable/permanent classifications without leaking SMTP details.
|
||
|
||
Done 2026-08-09: SQLite idempotency, provider-neutral injection, bounded
|
||
STARTTLS SMTP calls, duplicate suppression, and redacted provider-unavailable
|
||
responses are implemented. The same narrow service now issues public-
|
||
registration mailbox challenges: plaintext handles exist only in the message,
|
||
SQLite retains a SHA-256 digest, evidence is purpose/binding scoped, expiry is
|
||
enforced, and consumption is atomic and single-use. All 23 repository tests
|
||
pass.
|
||
|
||
2026-08-10 cancellation increment: verification messages now include a fixed
|
||
portal cancellation link using the same opaque handle. The SQLite store keeps
|
||
only its digest and atomically records either consumption or cancellation, so
|
||
verification and cancellation invalidate one another and replay fails closed.
|
||
Purpose-bound cancellation evidence lets user-engine abandon the matching
|
||
registration without accepting browser identity claims. All 24 tests pass.
|
||
|
||
## T03 - Establish custody and deploy the service
|
||
|
||
```task
|
||
id: EMAIL-WP-0004-T03
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "13428364-29b1-4e0d-aaf5-c0b254c829b9"
|
||
```
|
||
|
||
Route SMTP and caller credentials through the credential catalog before
|
||
requesting them, store provider material in OpenBao, and deliver only scoped
|
||
runtime secrets. Deploy an immutable image on railiance01 with probes,
|
||
resource/security controls, default-deny NetworkPolicy, restricted
|
||
user-engine ingress, SMTP-only egress, and rollback.
|
||
|
||
Done when user-engine can call the cluster-local receiver without possessing
|
||
SMTP credentials and unrelated workloads cannot send through it.
|
||
|
||
Done 2026-08-12 after CCR-2026-0010 approval:
|
||
|
||
* OpenBao path `platform/workloads/email-connect/transactional` provisioned
|
||
(ingest token new; SMTP username/password seeded from live forgejo IONOS
|
||
lane — sender ConfigMap uses `forgejo@coulomb.social`).
|
||
* Policies applied; ESO child token Secret
|
||
`external-secrets/openbao-email-connect-eso-token`; ClusterSecretStore
|
||
`openbao-email-connect` Ready; ExternalSecret SecretSynced (3 keys).
|
||
* Package applied on railiance01: Deployment Ready 1/1, PVC Bound, probes
|
||
green, image digest `sha256:bc7a01b4…`.
|
||
* Network: email-connect default-deny + user-engine ingress + SMTP-587 egress;
|
||
user-engine-runtime egress allow to email-connect:8080 (net-kingdom
|
||
`sso-mfa/k8s/user-engine/runtime.yaml`).
|
||
* Smoke: user-engine `/healthz`/`/readyz` 200; unauth `/v1/send` 401; bearer
|
||
`template_not_allowed` / `invalid_recipient` 400; default-ns probe
|
||
Connection refused. user-engine secret has no SMTP keys.
|
||
* Catalog `email-connect-transactional` promoted active/resolvable.
|
||
* Follow-up (not T03): mount same ingest token into user-engine for NK outbox
|
||
(NK-WP-0024); optional dedicated noreply@ mailbox; T04 failure proof.
|
||
|
||
## T04 - Prove invitation delivery failure behavior
|
||
|
||
```task
|
||
id: EMAIL-WP-0004-T04
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "34127b3e-f7c6-4ebe-a86c-743ba1b27640"
|
||
```
|
||
|
||
Test template allow-list denial, invalid recipient, provider timeout, temporary
|
||
deferral, permanent rejection, duplicate request, suppression, resend, and
|
||
redacted diagnostics. Verify mailbox ownership and provider acceptance never
|
||
alter user-engine authorization. Hand non-secret message/event references and
|
||
failure evidence back to NK-WP-0024.
|
||
|
||
Done when deployed invitation and verification flows are observable,
|
||
idempotent, retry-safe, and conservative about delivery evidence.
|
||
|
||
Done 2026-08-12:
|
||
|
||
* ProviderError classification: timeout/temporary/unavailable → 503
|
||
`retryable=true`; permanent_rejection → 422 `retryable=false`; no SMTP text
|
||
in responses. SQLite suppressions → `recipient_suppressed`. Accept body
|
||
carries `evidence_ceiling=provider_accepted`; verification consume/cancel set
|
||
`authorization=false`.
|
||
* Unit matrix: 9 tests pass (`tests/test_transactional.py`).
|
||
* Live matrix on railiance01 (image
|
||
`sha256:a9de1994…`): unauth 401, template/invalid/idempotency 400, accept
|
||
202 + duplicate 200, resend 202, bad domain 422 permanent_rejection,
|
||
suppression 400; redaction checked. Evidence note for NK-WP-0024:
|
||
`docs/EMAIL-WP-0004-T04-failure-evidence.md`.
|
||
* Workplan finished when T01–T04 all done (this completes EMAIL-WP-0004).
|