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

  1. Give each Messenger implementation a unique, explicit Name() return value
  2. Create a new Manager instance instead of reusing one across re-initializations
  3. Check before adding: if the messenger already exists, replace or skip it deliberately (remove and re-add if replacement is intended)
  4. 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

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.