fa3c4f2657
Raw-MIME passthrough (STARTTLS, AUTH LOGIN) — separate from the notify HTTP-API alert path (which drops inline CID images). Per-customer token-bucket rate limit, From-header allowlist backstop. Resend key stays hub-side. No new external dep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
3.5 KiB
Markdown
51 lines
3.5 KiB
Markdown
# felhom.eu — task reports
|
|
|
|
> **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md).
|
|
|
|
## App-email passthrough — hub leg (`POST /api/v1/mail` → Resend SMTP), hub v0.18.0
|
|
|
|
**Task:** SMTP app-relay (apps → on-box shim → hub → Resend). Implements
|
|
`documentation/audits/SPIKE-smtp-app-relay-2026-06-28.md` (verdict READY).
|
|
|
|
### Baseline (verified live)
|
|
- Hub `main` @ `4b97855`, version **v0.17.0** (the Resend-key rotation already shipped earlier today 2026-06-29)
|
|
→ target **v0.18.0**. (The prompt assumed v0.16.0→v0.17.0; the rotation took v0.17.0 first, so this leg is
|
|
v0.18.0.) Prerequisite satisfied: Resend key is out-of-band in `Secret/resend-api`, read via `RESEND_API_KEY`.
|
|
|
|
### Files
|
|
- **Created** `internal/mailrelay/relay.go` — `ResendSMTP` (`Sender`): STARTTLS to `smtp.resend.com:587`,
|
|
`AUTH LOGIN resend/<key>` (small stdlib `net/smtp.Auth` LOGIN impl), raw `MAIL`/`RCPT`/`DATA` **passthrough**.
|
|
`FromDomain` (From-header parser). **No new external dependency.**
|
|
- **Created** `internal/api/mail.go` — `handleMail`: `checkAuthCustomer` → From-domain allowlist (403 backstop)
|
|
→ per-customer token-bucket rate limit (429) → passthrough to Resend (200 / 502). `SetMailRelay` wiring +
|
|
`mailRateLimiter`.
|
|
- **Modified** `internal/api/handler.go` — sender/limiter/allowlist fields + `POST /api/v1/mail` route.
|
|
- **Modified** `cmd/hub/main.go` — `MailConfig` (`per_customer_per_minute`, `from_domains`) + wire `ResendSMTP`
|
|
when a key is present (else 503). The `notify/dispatcher.go` HTTP-API alert path is **untouched**.
|
|
|
|
### Green gate (local)
|
|
`go build ./... && go vet ./... && go test ./...` — **PASS** (6 packages ok, 0 failures).
|
|
|
|
**Tests & §10 companion red-proofs**
|
|
- **Passthrough byte-equality (§7 A / §10):** `TestMail_HappyPath_PassthroughRawBytes` — the `Sender` receives
|
|
the raw bytes unchanged (not a parsed payload). PASS.
|
|
- **From-reject + companion (§7 B / §10):** `TestMail_FromOutsideAllowlist_Rejected_NoSend` (403, sender never
|
|
called) + `TestMail_FromReject_CompanionProof` (allowing the domain reaches the sender). PASS.
|
|
- **Per-box rate limit + isolation + companion (§7 C / §10):** `TestMail_RateLimit_PerCustomer` (429 on the 2nd
|
|
at 1/min; a different customer unaffected) + `TestMail_RateLimit_CompanionProof` (generous limit lets N+1
|
|
through). PASS.
|
|
- Send-failure→502, 401/503/400 paths, token-bucket unit (injected clock), LOGIN auth + From-domain parse. PASS.
|
|
|
|
### Deployment & live validation
|
|
- **Deploy:** build `felhom-hub:0.18.0` on 180 → bump `manifests/hub.yaml` image → ArgoCD sync (auto-sync off).
|
|
The `mail` config is optional (defaults 30/min, `felhom.eu`); the Resend key is already injected via
|
|
`Secret/resend-api` (`RESEND_API_KEY`), so the relay activates on deploy.
|
|
- **End-to-end (app → shim → hub → Resend → real inbox):** result recorded here after the live run; method
|
|
stated. The Resend key is supplied to the hub out-of-band — never on the guest, never committed.
|
|
|
|
### Observations
|
|
- App-relay is a **separate** code path from the hub's own structured alerts (which keep using the Resend
|
|
**HTTP API**) — raw passthrough is required because the API path silently drops inline CID images (spike §4).
|
|
- v1: single-shot, no spool, no idempotency key. v2 would add accept-and-spool + `Resend-Idempotency-Key`.
|
|
- Fleet free-tier ceiling is 100 emails/day. No secrets in any committed file.
|