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
+41
View File
@@ -64,6 +64,17 @@ type Settings struct {
// App-to-app integration state (e.g., "onlyoffice:filebrowser" → state)
Integrations map[string]IntegrationState `json:"integrations,omitempty"`
// AppEmail is the global app-email (SMTP relay) toggle. When on, deployed apps with an
// smtp_mapping can send mail via the in-controller shim → hub → Resend. Relay-only:
// no BYO host/port/user/pass (that escape hatch is deferred).
AppEmail *AppEmail `json:"app_email,omitempty"`
}
// AppEmail holds the global app-email toggle and an optional household display name.
type AppEmail struct {
Enabled bool `json:"enabled"`
FromName string `json:"from_name,omitempty"` // optional household display name for the From line
}
// IntegrationState holds the state of a provider:target integration pair.
@@ -1178,6 +1189,36 @@ func (s *Settings) SetGeoSyncState(zoneID, rulesetID, syncError string) error {
return s.save()
}
// --- App email (SMTP relay) ---
// GetAppEmail returns the global app-email toggle (a copy; never the live pointer).
func (s *Settings) GetAppEmail() AppEmail {
s.mu.RLock()
defer s.mu.RUnlock()
if s.AppEmail == nil {
return AppEmail{}
}
return *s.AppEmail
}
// AppEmailEnabled reports whether app-email is globally on.
func (s *Settings) AppEmailEnabled() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.AppEmail != nil && s.AppEmail.Enabled
}
// SetAppEmail updates the global app-email toggle and persists it.
func (s *Settings) SetAppEmail(enabled bool, fromName string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] SetAppEmail enabled=%v from_name=%q", enabled, fromName)
}
s.AppEmail = &AppEmail{Enabled: enabled, FromName: strings.TrimSpace(fromName)}
return s.save()
}
// --- App-to-app integrations ---
// GetIntegrationState returns the state for a specific integration key (e.g., "onlyoffice:filebrowser").