v0.88.0: app-email SMTP relay (in-process shim + per-app injection)

In-process go-smtp shim (Shape 1): apps → shim → hub → Resend, Resend key stays
hub-side. From-header allowlist (reject 5xx pre-hub), single-shot raw-MIME forward,
status→SMTP mapping. Global + per-app toggles gate compose-time env injection from
.felhom.yml smtp_mapping. Hungarian UI on settings + app config pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 08:45:04 +02:00
parent 7cddb885e0
commit 0e20eb19c1
22 changed files with 1619 additions and 64 deletions
+26
View File
@@ -30,6 +30,26 @@ type Config struct {
System SystemConfig `yaml:"system"`
LocalAPI LocalAPIConfig `yaml:"local_api"`
Quiesce QuiesceConfig `yaml:"quiesce"`
MailRelay MailRelayConfig `yaml:"mail_relay"`
}
// MailRelayConfig tunes the in-controller SMTP shim (app email → shim → hub → Resend).
// The shim only runs when hub.enabled AND the global app-email toggle is on; it binds to
// the app Docker network ONLY (never published to host/internet). ShimHost is the DNS
// name injected into apps' SMTP_HOST (the controller's container name on the app network).
type MailRelayConfig struct {
// Enabled is a hard kill-switch: false disables the shim regardless of the runtime
// toggle. nil/true → the runtime app-email toggle decides. (Operational override only.)
Enabled *bool `yaml:"enabled"`
PlainListen string `yaml:"plain_listen"` // plaintext+STARTTLS, default ":2525"
TLSListen string `yaml:"tls_listen"` // implicit-TLS, default ":2465"
ShimHost string `yaml:"shim_host"` // app-network DNS name of the controller; default "felhom-controller"
FromDomains []string `yaml:"from_domains"` // From-header allowlist; default ["felhom.eu"]
}
// HardEnabled reports the operational kill-switch (default on unless explicitly false).
func (m MailRelayConfig) HardEnabled() bool {
return m.Enabled == nil || *m.Enabled
}
// LocalAPIConfig is the in-guest controller's handle on the host agent's per-guest local API
@@ -298,6 +318,12 @@ func applyDefaults(cfg *Config) {
d(&cfg.Quiesce.PollInterval, "5m")
d(&cfg.Quiesce.StatusPoll, "10s")
d(&cfg.Quiesce.MaxQuiesce, "30m")
d(&cfg.MailRelay.PlainListen, ":2525")
d(&cfg.MailRelay.TLSListen, ":2465")
d(&cfg.MailRelay.ShimHost, "felhom-controller")
if len(cfg.MailRelay.FromDomains) == 0 {
cfg.MailRelay.FromDomains = []string{"felhom.eu"}
}
}
func applyEnvOverrides(cfg *Config) {