release: 1.6.0: 30-day send stats and optional auto level-2 rate limits

30-day sending statistics per domain and application, plus optional auto level-2 rate limits derived from average send rate.
Added:
  - Domain page **Sending statistics** card: total messages, peak and average
  - Level-2 rate limit **Manual** / **Auto** mode on domain and application
  - Domain export JSON now includes rate limits (`mode`, `auto_multiplier`,
Changed:
  - Auto mode uses the level-1 window (`RATE_LIMIT_WINDOW_SECONDS`); manual

Full detail: CHANGELOG.md (1.6.0).
This commit is contained in:
mix
2026-09-22 15:24:52 +03:00
parent dca7d99c1b
commit 60b743e6c2
27 changed files with 1092 additions and 140 deletions
+5 -1
View File
@@ -254,7 +254,7 @@ func run() error {
}
var wg sync.WaitGroup
errc := make(chan error, 3)
errc := make(chan error, 4)
roles := []struct {
name string
@@ -272,6 +272,10 @@ func run() error {
return days
})
}},
{"rate-limit-recalc", func(ctx context.Context) error {
runAutoRateLimitRecalc(ctx, cfg, st)
return nil
}},
}
for _, r := range roles {
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"context"
"log"
"time"
"github.com/mixeme/selfpost/internal/logtail"
"github.com/mixeme/selfpost/internal/store"
)
// runAutoRateLimitRecalc recomputes auto-mode level-2 limits on the same
// interval as send-log retention pruning (6 hours).
func runAutoRateLimitRecalc(ctx context.Context, cfg config, st *store.Store) {
recalc := func() {
retention := cfg.retentionDays
if days, err := st.GetSendLogRetentionDays(cfg.retentionDays); err == nil {
retention = days
}
l1Max := cfg.rateLimitMessagesPerIP
if l1Max <= 0 {
l1Max = 100
}
l1Window := cfg.rateLimitWindowSeconds
if l1Window <= 0 {
l1Window = 3600
}
n, err := st.RecalcAllAutoRateLimits(retention, l1Max, l1Window)
if err != nil {
log.Printf("auto rate-limit recalc: %v", err)
return
}
if n > 0 {
log.Printf("auto rate-limit recalc: updated %d limit(s)", n)
}
}
recalc()
t := time.NewTicker(logtail.RetentionInterval())
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
recalc()
}
}
}