2026-08-09 21:28:41 +02:00
|
|
|
|
---
|
|
|
|
|
|
id: EMAIL-WP-0004
|
|
|
|
|
|
type: workplan
|
|
|
|
|
|
title: "Provide transactional invitation mail delivery"
|
|
|
|
|
|
domain: infotech
|
|
|
|
|
|
repo: email-connect
|
2026-08-12 13:32:11 +02:00
|
|
|
|
status: finished
|
2026-08-09 21:28:41 +02:00
|
|
|
|
owner: codex
|
|
|
|
|
|
topic_slug: netkingdom
|
|
|
|
|
|
created: "2026-08-08"
|
2026-08-12 13:32:11 +02:00
|
|
|
|
updated: "2026-08-12"
|
2026-08-09 21:28:41 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-12 13:32:11 +02:00
|
|
|
|
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”.
|
|
|
|
|
|
|
2026-08-09 21:28:41 +02:00
|
|
|
|
## 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
|
2026-08-10 12:29:55 +02:00
|
|
|
|
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-09 21:28:41 +02:00
|
|
|
|
|
2026-08-10 19:27:00 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-09 21:28:41 +02:00
|
|
|
|
## T03 - Establish custody and deploy the service
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: EMAIL-WP-0004-T03
|
2026-08-12 13:32:11 +02:00
|
|
|
|
status: done
|
2026-08-09 21:28:41 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-12 13:32:11 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-09 21:28:41 +02:00
|
|
|
|
## T04 - Prove invitation delivery failure behavior
|
|
|
|
|
|
|
|
|
|
|
|
```task
|
|
|
|
|
|
id: EMAIL-WP-0004-T04
|
2026-08-12 13:32:11 +02:00
|
|
|
|
status: done
|
2026-08-09 21:28:41 +02:00
|
|
|
|
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.
|
2026-08-12 13:32:11 +02:00
|
|
|
|
|
|
|
|
|
|
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).
|