# 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/` (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 — DONE (2026-06-29) - **Deployed** `felhom-hub:0.18.0`: built on 180 → bumped `manifests/hub.yaml` → ArgoCD `felhom` app hard-refresh + sync (auto-sync off). Rollout OK; live image `…felhom-hub:0.18.0`; startup log: `[INFO] App-email relay enabled (limit 30/min/customer, From domains [felhom.eu])`. The Resend key is injected from `Secret/resend-api` (`RESEND_API_KEY`); no key in any committed file. - **End-to-end (app → on-box shim → hub → Resend):** a raw MIME message with an inline CID image, From `vaultwarden@felhom.eu`, was relayed from guest 9201's shim through this hub to the live Resend account. Hub log: `/api/v1/mail: relayed for demo-felhom (from=vaultwarden@felhom.eu rcpts=1 bytes=789)`; the box's shim got `hub=200` and returned `DATA 250 "OK: queued"` to the sender (Resend accepted/queued for the verified `felhom.eu` domain). Operator inbox arrival is the final confirmation (sent to the operator's own address). - **Security backstop (live):** a send From `evil@notfelhom.example` was rejected at the box's shim (`550`) before ever reaching `/api/v1/mail` — the hub From-allowlist + rate-limit are the documented second backstop. ### 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.