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>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/api"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/assets"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/mailrelay"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/monitor"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/notify"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
@@ -65,6 +66,31 @@ type Config struct {
|
||||
// with DEFAULT_MIN_CONTROLLER_VERSION.
|
||||
DefaultMinVersion string `yaml:"default_min_version"`
|
||||
} `yaml:"controller_updates"`
|
||||
Mail MailConfig `yaml:"mail"`
|
||||
}
|
||||
|
||||
// MailConfig tunes the app-email passthrough (POST /api/v1/mail).
|
||||
type MailConfig struct {
|
||||
// PerCustomerPerMinute caps a single customer's forwarded messages per minute (abuse
|
||||
// containment — one box can't drain the shared Resend quota). Default 30.
|
||||
PerCustomerPerMinute int `yaml:"per_customer_per_minute"`
|
||||
// FromDomains is the From-header allowlist (backstop; Resend is the final backstop).
|
||||
// Default ["felhom.eu"].
|
||||
FromDomains []string `yaml:"from_domains"`
|
||||
}
|
||||
|
||||
func (m MailConfig) effectivePerMinute() int {
|
||||
if m.PerCustomerPerMinute <= 0 {
|
||||
return 30
|
||||
}
|
||||
return m.PerCustomerPerMinute
|
||||
}
|
||||
|
||||
func (m MailConfig) effectiveFromDomains() []string {
|
||||
if len(m.FromDomains) == 0 {
|
||||
return []string{"felhom.eu"}
|
||||
}
|
||||
return m.FromDomains
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -205,6 +231,18 @@ func main() {
|
||||
)
|
||||
apiHandler.SetDispatcher(dispatcher)
|
||||
|
||||
// App-email passthrough (POST /api/v1/mail): a customer box's shim forwards a raw message
|
||||
// here and the hub re-emits it to Resend over SMTP, UNCHANGED (raw passthrough — NOT the
|
||||
// dispatcher's structured HTTP-API path, which drops inline CID images). The Resend key stays
|
||||
// hub-side. Wired only when a key is present; otherwise the endpoint returns 503.
|
||||
if cfg.Notifications.ResendAPIKey != "" {
|
||||
mailSender := mailrelay.NewResendSMTP(cfg.Notifications.ResendAPIKey)
|
||||
apiHandler.SetMailRelay(mailSender, cfg.Mail.PerCustomerPerMinute, cfg.Mail.FromDomains)
|
||||
logger.Printf("[INFO] App-email relay enabled (limit %d/min/customer, From domains %v)", cfg.Mail.effectivePerMinute(), cfg.Mail.effectiveFromDomains())
|
||||
} else {
|
||||
logger.Printf("[INFO] App-email relay disabled (no Resend key)")
|
||||
}
|
||||
|
||||
webServer := web.New(dataStore, cfg.Auth.PasswordHash, cfg.API.ReportAPIKey, Version, staleThreshold, logger)
|
||||
webServer.SetTemplateFetcher(templateFetcher)
|
||||
webServer.SetAssetManager(assetsMgr)
|
||||
|
||||
Reference in New Issue
Block a user