knadh/listmonk · error

error fetching attachment %d on campaign %s: %v

Error message

error fetching attachment %d on campaign %s: %v

What it means

While preparing a campaign for sending, each media/attachment ID in the campaign's MediaIDs is fetched from the store via GetAttachment and appended to the campaign. If any attachment cannot be retrieved (missing row, DB error), this wrapped error identifying the attachment ID and campaign name is returned and campaign preparation aborts.

Source

Thrown at internal/manager/manager.go:701

	return funcs
}

// attachMedia loads any media/attachments from the media store and attaches
// the byte blobs to the campaign. Inline attachments are skipped as they're
// loaded earlier by LoadInlineImages().
func (m *Manager) attachMedia(c *models.Campaign) error {
	// Already loaded if any non-inline attachment is present.
	for _, a := range c.Attachments {
		if !a.IsInline {
			return nil
		}
	}

	for _, mid := range []int64(c.MediaIDs) {
		a, err := m.store.GetAttachment(int(mid))
		if err != nil {
			return fmt.Errorf("error fetching attachment %d on campaign %s: %v", mid, c.Name, err)
		}
		c.Attachments = append(c.Attachments, a)
	}

	return nil
}

// LoadInlineImages resolves any <img ... data-embed ...> tags in the campaign
// body and template body one time before CompileTemplate.
func (m *Manager) LoadInlineImages(c *models.Campaign) error {
	if c.ContentType == models.CampaignContentTypePlain {
		return nil
	}

	cidCache := make(map[string]string)
	body, atts := m.applyInlineImages(c.Body, cidCache)
	c.Body = body

View on GitHub (pinned to 670c01717d)

Solutions

  1. Re-attach valid media to the campaign or remove the dangling IDs from the campaign's MediaIDs
  2. Restore the missing attachment row/file (re-upload the file via admin UI)
  3. Check DB health if GetAttachment fails for existing IDs (connection, permissions)
  4. Prune stale references: scan campaigns for MediaIDs with no matching media rows and clean them up

Example fix

// before
MediaIDs: [3, 99] // 99 was deleted
// after
MediaIDs: [3] // or re-upload the file and use its new ID
Defensive patterns

Strategy: try-catch

Validate before calling

// Audit campaign media references before sending
for _, id := range c.MediaIDs {
    if !mediaExists(int(id)) {
        return fmt.Errorf("campaign %q references missing media %d", c.Name, id)
    }
}

Try / catch

if err := m.LoadAttachments(c); err != nil {
    if strings.Contains(err.Error(), "error fetching attachment") {
        log.Printf("dangling media on campaign %s, pruning: %v", c.Name, err)
        return pruneMissingMedia(c) // drop bad IDs and retry
    }
    return err
}

Prevention

When it happens

Trigger: A campaign references a media/attachment ID that was deleted from the media store, an ID belonging to a different installation, or the database lookup fails (connection error, corruption) during LoadAttachments.

Common situations: Admins deleting uploaded media that is still attached to a campaign, DB migrations losing media rows, restoring a campaign from backup without its media, or hardcoding attachment IDs in seed data.

Related errors


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/d1b1eb26015af3ad. Report an issue: GitHub.