diff --git a/internal/app/campaign/handlers.go b/internal/app/campaign/handlers.go index 0f062ad8..e0956d74 100644 --- a/internal/app/campaign/handlers.go +++ b/internal/app/campaign/handlers.go @@ -604,8 +604,8 @@ func (s *campaignService) enqueueCampaignWakeup(ctx context.Context, campaignID case errors.Is(err, scheduler.ErrNoEligibleMailbox): _ = s.campaignRepository.UpdateStatusWithLock(ctx, campaignID, "paused_no_accounts") return errx.New(errx.BadRequest, - "this campaign's mailboxes are all outside their sending window or over their daily limit right now; "+ - "check each mailbox's timezone, sending behaviour and daily cap") + "no mailbox on this campaign can send under its current sending settings; "+ + "check each mailbox's sending behaviour profile (working days) and timezone") case errors.Is(err, scheduler.ErrNoEmailAccounts): _ = s.campaignRepository.UpdateStatusWithLock(ctx, campaignID, "paused_no_accounts") return errx.New(errx.BadRequest, "no active email accounts found for campaign's email tags") diff --git a/internal/tasks/auto_pause_reason_test.go b/internal/tasks/auto_pause_reason_test.go index 5d78e32a..6e63a461 100644 --- a/internal/tasks/auto_pause_reason_test.go +++ b/internal/tasks/auto_pause_reason_test.go @@ -25,7 +25,7 @@ func TestAutoPauseReason(t *testing.T) { { name: "no eligible mailbox", err: scheduler.ErrNoEligibleMailbox, - want: "Campaign auto-paused: every mailbox is outside its sending window or over its daily budget", + want: "Campaign auto-paused: no mailbox can send under its current sending settings (check each mailbox's sending behaviour profile and timezone)", }, { name: "generic no accounts", diff --git a/internal/tasks/campaign_task.go b/internal/tasks/campaign_task.go index e2fe204d..9da8deb0 100644 --- a/internal/tasks/campaign_task.go +++ b/internal/tasks/campaign_task.go @@ -204,9 +204,13 @@ func (s *tasksService) HandleCampaignTask(task *proto.ProcessTask) *errx.Error { } if errors.Is(err, scheduler.ErrCampaignDeferred) { // A valid contact exists but no eligible mailbox right now (ESP-strict - // has no same-provider mailbox, or the daily new-lead cap is reached). + // has no same-provider mailbox, the daily new-lead cap is reached, or + // every mailbox has spent its daily budget or is outside its hours). // Reschedule at the deferred slot WITHOUT sending and WITHOUT touching // progress / daily counters / rotation — mirrors the daily-limit path. + // This task completes without a send, and completing it must not + // spend the mailbox's budget either (issue #306): the budget counts + // reserved sends, never bare wake-ups. // Capped: the next-due moment can be days out, and until this chain // wakes nothing re-reads the campaign, so leads imported meanwhile // would sit queued until then. @@ -908,7 +912,7 @@ func autoPauseReason(err error) string { case errors.Is(err, scheduler.ErrDomainAuthFailing): return "Campaign auto-paused: every mailbox is sending from a domain that fails SPF or DMARC authentication" case errors.Is(err, scheduler.ErrNoEligibleMailbox): - return "Campaign auto-paused: every mailbox is outside its sending window or over its daily budget" + return "Campaign auto-paused: no mailbox can send under its current sending settings (check each mailbox's sending behaviour profile and timezone)" default: return "Campaign auto-paused: no active email accounts available" }