Files
felhom.eu/REPORT.md
T
admin fa3c4f2657 hub v0.18.0: app-email passthrough POST /api/v1/mail → Resend SMTP
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>
2026-06-29 08:45:22 +02:00

3.5 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

  • 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.