Billionmail/BillionMail · error

failed to get recipient count: %w

Error message

failed to get recipient count: %w

What it means

WarmupCampaignService.CalculateEstimatedTime counts unsent recipients with SELECT COUNT(*) on recipient_info WHERE task_id = ? AND is_sent = 0. If the count query fails — DB unreachable, bad credentials, missing table/column, or cancelled context — it returns -1 and wraps the cause as 'failed to get recipient count'. Called from AssociateCampaignWithWarmup, this blocks associating a campaign with a warmup task.

Source

Thrown at core/internal/service/warmup/warmup_with_campaigns.go:110

		g.Log().Errorf(ctx, "AssociateCampaignWithWarmup: failed to create association for task ID %d and warmup ID %d: %v", taskId, warmupId, err)
		return nil, err
	}

	result := &CampaignWarmupAssociation{
		CampaignWarmup:   *newLink,
		EstimatedSeconds: estimatedSeconds,
	}

	g.Log().Infof(ctx, "Successfully associated task ID %d with warmup ID %d (IP: %s). Estimated time: %d seconds.", taskId, warmupId, senderIp, estimatedSeconds)
	return result, nil
}

// CalculateEstimatedTime calculates the estimated sending time in seconds for a given task and IP.
func (s *WarmupCampaignService) CalculateEstimatedTime(ctx context.Context, taskId int64, senderIp string) (int64, error) {
	// Get the total number of unsent recipients
	unsentCount, err := g.DB().Model("recipient_info").Ctx(ctx).Where("task_id", taskId).Where("is_sent", 0).Count()
	if err != nil {
		return -1, fmt.Errorf("failed to get recipient count: %w", err)
	}

	if unsentCount == 0 {
		return 0, nil
	}

	// Calculate the total hourly sending rate
	totalHourlyRate := 0
	providerGroups := []string{
		consts.MailProviderGroupGmail,
		consts.MailProviderGroupYahoo,
		consts.MailProviderGroupOutlook,
		consts.MailProviderGroupApple,
		consts.MailProviderGroupProton,
		consts.MailProviderGroupZoho,
		consts.MailProviderGroupAmazon,
		consts.MailProviderGroupOther,
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Inspect the wrapped error for the concrete DB cause (connection vs schema).
  2. Verify recipient_info exists with task_id and is_sent columns; run migrations.
  3. Test DB connectivity/credentials for the environment's config.
  4. Check DB load/pool settings if it fails only under load.
  5. Retry transient failures with backoff before failing campaign association.

Example fix

// before
unsentCount, err := g.DB().Model("recipient_info").Ctx(ctx).Where("task_id", taskId).Where("is_sent", 0).Count()
if err != nil {
    return -1, fmt.Errorf("failed to get recipient count: %w", err)
}
// after
unsentCount, err := g.DB().Model("recipient_info").Ctx(ctx).
    Where("task_id", taskId).Where("is_sent", 0).Count()
if err != nil {
    return -1, fmt.Errorf("failed to get recipient count for task %d: %w", taskId, err)
}
Defensive patterns

Strategy: retry

Validate before calling

// verify warmup schema before associating a campaign
if _, err := g.DB().Ctx(ctx).Model("recipient_info").Limit(1).One(); err != nil {
    return fmt.Errorf("recipient_info table unavailable: %w", err)
}
if err := g.DB().Ctx(ctx).Exec("SELECT 1"); err != nil {
    return fmt.Errorf("database unavailable: %w", err)
}

Try / catch

secs, err := svc.CalculateEstimatedTime(ctx, taskId, senderIp)
if err != nil {
    if strings.Contains(err.Error(), "failed to get recipient count") {
        // check ctx and DB health, retry transient failures
        if ctx.Err() != nil {
            return fmt.Errorf("cancelled: %w", ctx.Err())
        }
    }
    return err
}
if secs < 0 {
    return errors.New("unexpected negative estimate")
}

Prevention

When it happens

Trigger: recipient_info table missing or renamed, DB connection failure/timeout, context cancelled during the count, task_id column absent after schema drift.

Common situations: Deployments with un-run migrations, transient DB outages during warmup scheduling, pool exhaustion under load, wrong environment config pointing at a DB without the warmup schema.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/dbf61894cffc841b. Report an issue: GitHub.