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
- Re-attach valid media to the campaign or remove the dangling IDs from the campaign's MediaIDs
- Restore the missing attachment row/file (re-upload the file via admin UI)
- Check DB health if GetAttachment fails for existing IDs (connection, permissions)
- 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
- Prevent deletion of media that campaigns reference (soft-delete with reference check)
- Re-upload/restore media after DB restores before restarting campaigns
- Periodically scan campaigns for MediaIDs missing from the media table
- Avoid hardcoding attachment IDs in seed/fixture data
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
- unknown messenger %s on campaign %s
- campaigns.fieldInvalidFromEmail
- campaigns.fieldInvalidName
- campaigns.fieldInvalidSubject
- campaigns.fieldInvalidSendAt
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/d1b1eb26015af3ad.
Report an issue: GitHub.