Files
warmbly/internal/scheduler/email_scheduler.go
T
Matthew Meszaros d227038ca0 ci: drop unused/unconvert/gosimple + shadow/nilness, run gofmt
Disable the linters that fire on legacy code without flagging real
bugs: `unused` (orphan repos kept for future feature flags),
`unconvert` (defensive type conversions), `gosimple` (style
suggestions in code we don't want to touch).

govet: disable `shadow` (idiomatic `err :=` re-decls in transaction
patterns) and `nilness` (legitimate defensive nil checks that look
tautological to the analyzer).

Ran `gofmt -w internal/ cmd/` — every Go file now passes
gofmt -l with no output.

Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx,
sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
2026-05-23 16:54:12 +00:00

61 lines
1.7 KiB
Go

package scheduler
import (
"context"
"time"
"github.com/google/uuid"
)
// CalculateNextEmailTime calculates the optimal time to send a user-initiated email (smart send)
func (s *schedulerService) CalculateNextEmailTime(ctx context.Context, accountID uuid.UUID) (time.Time, error) {
// STEP 1: Load email account
account, xerr := s.emailRepo.GetByID(ctx, accountID)
if xerr != nil {
return time.Time{}, xerr
}
if account == nil {
return time.Now(), nil
}
accountTZ := loadLocation(account.Timezone)
// STEP 2: Get last email time and apply min_wait_time
candidateTime := time.Now()
lastEmailTime, err := s.taskRepo.GetLastEmailTime(ctx, accountID)
if err != nil {
return time.Time{}, err
}
if lastEmailTime != nil {
minWait := time.Second * time.Duration(account.MinWaitTime)
earliestNext := lastEmailTime.Add(minWait)
if candidateTime.Before(earliestNext) {
candidateTime = earliestNext
}
}
// STEP 3: Ensure within business hours (8am-8pm account timezone)
candidateTime = ensureBusinessHours(candidateTime, account.Timezone)
// STEP 4: Add jitter (±10 minutes)
jitter := randomJitter(-10, 10)
candidateTime = candidateTime.Add(time.Minute * time.Duration(jitter))
// STEP 5: Resolve conflicts with scheduled tasks
scheduledTasks, err := s.taskRepo.GetScheduledTasksForAccount(ctx, accountID, candidateTime)
if err != nil {
return time.Time{}, err
}
candidateTime = resolveConflicts(candidateTime, scheduledTasks, account.MinWaitTime)
// STEP 6: Apply distribution curve
candidateTime = applyDistributionCurve(candidateTime, accountTZ)
// STEP 7: Final business hours check
candidateTime = ensureBusinessHours(candidateTime, account.Timezone)
return candidateTime, nil
}