Billionmail/BillionMail · error

failed to update task process status: %w

Error message

failed to update task process status: %w

What it means

UpdateTaskProcessStatus sets task_process on an email_tasks row during task processing. Any UPDATE error is wrapped as "failed to update task process status: %w". ProcessTask relies on this to track progress, so failures here leave task state stale even if sending continued.

Source

Thrown at core/internal/service/batch_mail/batch_mail.go:571

		Update()

	if err != nil {
		return fmt.Errorf("failed to update task pause status: %w", err)
	}

	g.Log().Info(ctx, "Updated task %d pause status: isPaused=%v, task_process=%d", taskId, isPaused, processValue)
	return nil
}

// UpdateTaskProcessStatus update task process status
func UpdateTaskProcessStatus(ctx context.Context, taskId int, status int) error {
	_, err := g.DB().Model("email_tasks").
		Where("id", taskId).
		Data(g.Map{"task_process": status}).
		Update()

	if err != nil {
		return fmt.Errorf("failed to update task process status: %w", err)
	}

	return nil
}

// GetTaskSendingStats get task sending stats (success count and failed count)
func GetTaskSendingStats(ctx context.Context, taskID int) (int, int, error) {
	if taskID <= 0 {
		return 0, 0, nil
	}

	// query success count
	successQuery := g.DB().Model("mailstat_send_mails sm")
	successQuery = successQuery.LeftJoin("mailstat_message_ids mid", "sm.postfix_message_id=mid.postfix_message_id")
	successQuery = successQuery.LeftJoin("recipient_info ri", "mid.message_id=ri.message_id")
	successQuery = successQuery.Where("ri.task_id", taskID)
	successQuery = successQuery.Where("sm.status", "sent")
	successQuery = successQuery.Where("sm.dsn LIKE '2.%'")

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped underlying error and DB connectivity
  2. Verify UPDATE permissions on email_tasks and primary-instance routing
  3. Increase statement/idle timeouts for long-running send workers
  4. Retry the status update (it is idempotent for a given status value)

Example fix

// before: single attempt, fire and forget failure path
_, err := g.DB().Model("email_tasks").Where("id", taskId).Data(g.Map{"task_process": status}).Update()
if err != nil {
    return fmt.Errorf("failed to update task process status: %w", err)
}
// after: retry idempotent status update
var err error
for i := 0; i < 3; i++ {
    if _, err = g.DB().Model("email_tasks").Where("id", taskId).Data(g.Map{"task_process": status}).Update(); err == nil {
        return nil
    }
    time.Sleep(time.Duration(i+1) * 500 * time.Millisecond)
}
return fmt.Errorf("failed to update task process status: %w", err)
Defensive patterns

Strategy: retry

Validate before calling

if taskId <= 0 {
    return errors.New("invalid taskId")
}
if status < 0 {
    return errors.New("invalid process status")
}

Try / catch

if err := UpdateTaskProcessStatus(ctx, taskId, status); err != nil {
    if isTransientDBError(err) {
        // retry once or twice; the update is idempotent
    }
    log.Printf("task %d status update failed: %v", taskId, err)
    return err
}

Prevention

When it happens

Trigger: ProcessTask updating the process status while the DB UPDATE fails: connection drop mid-send, statement timeout, UPDATE permission denied, or trigger/constraint rejection on email_tasks.

Common situations: Long campaigns where the DB connection goes stale; failover during a large batch send; DB user restricted to read-only; table locks from analytics queries scanning email_tasks.

Related errors


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