knadh/listmonk · error

error fetching campaign subscribers (%s): %v

Error message

error fetching campaign subscribers (%s): %v

What it means

NextSubscribers pulls the next batch of subscribers for a running campaign from the store. If the store query fails, the error is wrapped with the campaign name and returned; the caller treats this as a batch-fetch failure and stops message dispatch for that iteration.

Source

Thrown at internal/manager/pipe.go:85

		p.cleanup()
	}()

	m.pipesMut.Lock()
	m.pipes[c.ID] = p
	m.pipesMut.Unlock()
	return p, nil
}

// NextSubscribers processes the next batch of subscribers in a given campaign.
// It returns a bool indicating whether any subscribers were processed
// in the current batch or not. A false indicates that all subscribers
// have been processed, or that a campaign has been paused or cancelled.
func (p *pipe) NextSubscribers() (bool, error) {
	// Fetch the next batch of subscribers from a 'running' campaign.
	subs, err := p.m.store.NextSubscribers(p.camp.ID, p.m.cfg.BatchSize)
	if err != nil {
		return false, fmt.Errorf("error fetching campaign subscribers (%s): %v", p.camp.Name, err)
	}

	// There are no subscribers from the query. Either all subscribers on the campaign
	// have been processed, or the campaign has changed from 'running' to 'paused' or 'cancelled'.
	if len(subs) == 0 {
		return false, nil
	}

	// Is there a sliding window limit configured?
	hasSliding := p.m.cfg.SlidingWindow &&
		p.m.cfg.SlidingWindowRate > 0 &&
		p.m.cfg.SlidingWindowDuration.Seconds() > 1

	// Push messages.
	for _, s := range subs {
		msg, err := p.newMessage(s)
		if err != nil {
			p.m.log.Printf("error rendering message (%s) (%s): %v", p.camp.Name, s.Email, err)

View on GitHub (pinned to 670c01717d)

Solutions

  1. Check database connectivity and error logs for the underlying store error (it's wrapped in the %v)
  2. Re-validate the campaign's custom subscriber query — run validateQueryTables/executable SQL directly against the DB
  3. Retry the send: the pipe stops for this iteration but the campaign can resume once the DB is healthy
  4. Check for schema mismatches after upgrades/migrations and run pending migrations
Defensive patterns

Strategy: retry

Validate before calling

// Sanity-check the campaign's subscriber query before starting the send
if err := validateQueryTables(camp.SubscriberQuery); err != nil {
    return fmt.Errorf("invalid subscriber query for campaign %s: %w", camp.Name, err)
}

Try / catch

for attempt := 0; attempt < 3; attempt++ {
    hasMore, err := p.NextSubscribers()
    if err == nil { break }
    if !isTransientDBError(err) { return err }
    time.Sleep(backoff(attempt)) // transient DB outage: retry with backoff
}

Prevention

When it happens

Trigger: The store's NextSubscribers query fails — DB connection loss mid-campaign, an invalid custom subscriber query saved on the campaign (failing validation/execution), lock timeouts on the subscribers table, or the underlying query errors due to schema drift.

Common situations: Database restarts/failovers while a large campaign is sending, long-running export/lock contention with a custom segment query, DB credential rotation mid-send, or a custom query segment that was edited to invalid SQL after the campaign started.

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 knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/489d7232ea87c850. Report an issue: GitHub.