knadh/listmonk · error
messenger '%s' is already loaded
Error message
messenger '%s' is already loaded
What it means
Manager.AddMessenger registers a messaging backend (email/SMS provider) by the name returned by msg.Name(). If a messenger with that name is already registered in the manager, the second registration is rejected with this error to keep the messenger map unambiguous.
Source
Thrown at internal/manager/manager.go:203
messengers: make(map[string]Messenger),
pipes: make(map[int]*pipe),
tpls: make(map[int]*models.Template),
links: make(map[string]string),
nextPipes: make(chan *pipe, 1000),
campMsgQ: make(chan CampaignMessage, cfg.Concurrency*cfg.MessageRate*2),
msgQ: make(chan models.Message, cfg.Concurrency*cfg.MessageRate*2),
slidingStart: time.Now(),
}
m.tplFuncs = m.makeGnericFuncMap()
return m
}
// AddMessenger adds a Messenger messaging backend to the manager.
func (m *Manager) AddMessenger(msg Messenger) error {
id := msg.Name()
if _, ok := m.messengers[id]; ok {
return fmt.Errorf("messenger '%s' is already loaded", id)
}
m.messengers[id] = msg
return nil
}
// PushMessage pushes an arbitrary non-campaign Message to be sent out by the workers.
// It times out if the queue is busy.
func (m *Manager) PushMessage(msg models.Message) error {
t := time.NewTicker(pushTimeout)
defer t.Stop()
select {
case m.msgQ <- msg:
case <-t.C:
m.log.Printf("message push timed out: '%s'", msg.Subject)
return errors.New("message push timed out")
}View on GitHub (pinned to 670c01717d)
Solutions
- Give each Messenger implementation a unique, explicit Name() return value
- Create a new Manager instance instead of reusing one across re-initializations
- Check before adding: if the messenger already exists, replace or skip it deliberately (remove and re-add if replacement is intended)
- Fix empty/duplicate names from config-driven messenger factories
Example fix
// before
func (s *MySender) Name() string { return "" } // collides
// after
func (s *MySender) Name() string { return "my-sender" } Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check uniqueness of messenger names before registration
names := map[string]bool{}
for _, msgr := range messengers {
if names[msgr.Name()] { return fmt.Errorf("duplicate messenger name: %s", msgr.Name()) }
names[msgr.Name()] = true
} Try / catch
if err := mgr.AddMessenger(msgr); err != nil {
if strings.Contains(err.Error(), "is already loaded") {
log.Printf("messenger %q already registered, skipping", msgr.Name())
return nil
}
return err
} Prevention
- Give every Messenger a unique, explicit Name() return value
- Construct a fresh Manager per app lifecycle instead of re-adding messengers to a shared one
- In tests, create a new Manager per test or dedupe by name before adding
- Never leave Name() returning "" or a package-level default for multiple implementations
When it happens
Trigger: Calling AddMessenger twice with two Messenger implementations whose Name() returns the same string, or re-initializing the app (e.g. in tests or hot-reload) without creating a fresh Manager.
Common situations: Registering both 'smtp' and a custom sender that forgot to override Name() (empty or default name), initializing the manager in a loop during config reloads, or test suites adding the same messenger across subtests on a shared manager.
Related errors
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/0ea325b639ff9963.
Report an issue: GitHub.