Files
felhom.eu/REPORT.md
T
2026-06-29 08:58:06 +02:00

4.1 KiB

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.

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.goResendSMTP (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.gohandleMail: 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.goMailConfig (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.