mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 16:01:28 +00:00
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
package tasks
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
// warmupContent is the rendered content for one warmup send plus the cohort
|
|
// metadata recorded on the verification token for the A/B harness.
|
|
type warmupContent struct {
|
|
subject string
|
|
body string
|
|
theme string
|
|
contentSource string
|
|
conversationID *uuid.UUID
|
|
}
|
|
|
|
// getWarmupSettings returns the warmup generation settings with a short
|
|
// in-process TTL cache so the per-send AI-vs-static decision doesn't hit
|
|
// Postgres on every warmup send. Falls back to defaults when unconfigured.
|
|
func (s *tasksService) getWarmupSettings(ctx context.Context) models.WarmupGenerationSettings {
|
|
if s.warmupContentRepo == nil {
|
|
return models.DefaultWarmupGenerationSettings()
|
|
}
|
|
if c := s.warmupSettings; c != nil {
|
|
c.mu.RLock()
|
|
fresh := !c.fetched.IsZero() && time.Since(c.fetched) < 60*time.Second
|
|
val := c.val
|
|
c.mu.RUnlock()
|
|
if fresh {
|
|
return val
|
|
}
|
|
}
|
|
set, err := s.warmupContentRepo.GetGenerationSettings(ctx)
|
|
if err != nil || set == nil {
|
|
return models.DefaultWarmupGenerationSettings()
|
|
}
|
|
if c := s.warmupSettings; c != nil {
|
|
c.mu.Lock()
|
|
c.val = *set
|
|
c.fetched = time.Now()
|
|
c.mu.Unlock()
|
|
}
|
|
return *set
|
|
}
|
|
|
|
// pickNewWarmupContent selects content for a NEW (non-reply) warmup send. When
|
|
// AI enrichment is enabled and the dice roll falls within AISelectionShare it
|
|
// draws a thread from the cached, segment-aware AI bank; otherwise it uses the
|
|
// static in-code library. The static library is always the safe fallback, so
|
|
// an empty bank or disabled generator never stops warmup.
|
|
func (s *tasksService) pickNewWarmupContent(ctx context.Context, account Email) warmupContent {
|
|
settings := s.getWarmupSettings(ctx)
|
|
|
|
if settings.Enabled && s.warmupContentRepo != nil && rand.Intn(100) < settings.AISelectionShare {
|
|
// Content is drawn from the shared library by segment; tier doesn't
|
|
// affect content (only mailbox reputation isolation uses the pool).
|
|
segment := strings.TrimSpace(account.WarmupTag)
|
|
conv, err := s.warmupContentRepo.PickConversation(ctx, segment)
|
|
if err == nil && conv != nil {
|
|
c := Conversation{ID: conv.ID, Theme: conv.Theme, Description: conv.Description, Messages: conv.Messages}
|
|
body := GenerateConversationOpeningEmail(c, account)
|
|
subject := spinClean(conv.Subject)
|
|
if subject == "" {
|
|
subject = generateWarmupSubject()
|
|
}
|
|
id := conv.ID
|
|
return warmupContent{
|
|
subject: subject,
|
|
body: body,
|
|
theme: conv.Theme,
|
|
contentSource: models.WarmupContentSourceAI,
|
|
conversationID: &id,
|
|
}
|
|
}
|
|
}
|
|
|
|
conversation := randomWarmupConversation()
|
|
staticID := conversation.ID
|
|
return warmupContent{
|
|
subject: generateWarmupSubject(),
|
|
body: GenerateConversationEmail(conversation, account, false),
|
|
theme: conversation.Theme,
|
|
contentSource: models.WarmupContentSourceStatic,
|
|
conversationID: &staticID,
|
|
}
|
|
}
|