ory/hydra · warning

message not found

Error message

message not found

What it means

DeleteOne on the in-memory mailhog store returns "message not found" when the given message id is absent from memory.MessageIDIndex. The delete path looks up the index position first and fails fast if the id was never stored or was already deleted.

Source

Thrown at oryx/mailhog/memory.go:182

	for i := start; i > end; i-- {
		//for _, m := range memory.MessageIndex[start:end] {
		messages = append(messages, *memory.Messages[i])
	}

	msgs := data.Messages(messages)
	return &msgs, nil
}

// DeleteOne deletes an individual message by storage ID
func (memory *InMemory) DeleteOne(id string) error {
	memory.mu.Lock()
	defer memory.mu.Unlock()

	var index int
	var ok bool

	if index, ok = memory.MessageIDIndex[id]; !ok && true {
		return errors.New("message not found")
	}

	delete(memory.MessageIDIndex, id)
	for k, v := range memory.MessageIDIndex {
		if v > index {
			memory.MessageIDIndex[k] = v - 1
		}
	}
	memory.Messages = append(memory.Messages[:index], memory.Messages[index+1:]...)
	return nil
}

// DeleteAll deletes all in memory messages
func (memory *InMemory) DeleteAll() error {
	memory.mu.Lock()
	defer memory.mu.Unlock()
	memory.Messages = make([]*data.Message, 0)
	memory.MessageIDIndex = make(map[string]int)

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Check the id against the current message list (List/Search) before deleting.
  2. Handle the error idempotently: treat "message not found" as success for DELETE semantics if your API allows it.
  3. Avoid holding ids across store restarts; re-fetch the list after restart since the memory store resets.
  4. Guard against concurrent deletes (e.g. two goroutines deleting the same id) with application-level dedup.

Example fix

// before
if err := store.DeleteOne(ctx, msgID); err != nil { return err }
// after
if err := store.DeleteOne(ctx, msgID); err != nil && err.Error() != "message not found" {
	return err
} // treat already-deleted as idempotent success
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the message exists before deleting
ids, _ := store.List(ctx)
exists := false
for _, m := range ids {
	if m.ID == id { exists = true; break }
}
if !exists { return nil } // nothing to delete, skip call

Try / catch

err := store.DeleteOne(ctx, id)
if err != nil && strings.Contains(err.Error(), "message not found") {
	// idempotent DELETE: already gone, treat as success
	return nil
} else if err != nil {
	return err
}

Prevention

When it happens

Trigger: Calling memory.DeleteOne(ctx, id) with an id that does not exist in MessageIDIndex — e.g. an id from a restarted/reseeded store, an already-deleted message, or a client-supplied id that was never valid.

Common situations: Double DELETE calls from a UI or test, stale message list rendered before another client deleted the message, ids issued by a previous process lifetime (memory store is not persistent).

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/11b6347a0e3b5760. Report an issue: GitHub.