knadh/listmonk · warning

token was not found or has expired

Error message

token was not found or has expired

What it means

Err is the sentinel error of the tmptokens package: temporary tokens (used for unsubscribe/previews/campaign archive access) are stored in an in-memory map with expiry and usage count. Lookup returns Err when the token is absent, expired, or its Count is exhausted. Callers surface it as invalid-link/404 style responses.

Source

Thrown at internal/tmptokens/tmptokens.go:27

	"time"
)

const (
	// maxTries is the maximum number of verification attempts allowed for a token.
	// After this many failed checks, the token is automatically deleted.
	maxTries = 15
)

// Token represents a temporary token with TTL and arbitrary data.
type Token struct {
	TTL       time.Duration
	CreatedAt time.Time
	Count     int
	Data      any
}

var (
	Err = errors.New("token was not found or has expired")

	tokens = make(map[string]Token)
	mu     sync.RWMutex
)

func init() {
	// Start periodic cleanup of expired temporary tokens (2FA, password reset).
	go func() {
		ticker := time.NewTicker(time.Hour)
		defer ticker.Stop()
		for range ticker.C {
			Clean()
		}
	}()
}

// Set stores a token with the given ID, TTL, and data.
// If a token with the same ID already exists, it will be overwritten silently.

View on GitHub (pinned to 670c01717d)

Solutions

  1. Regenerate the link (re-send the campaign or re-create the archive token) so a fresh token is issued.
  2. Check whether the process restarted between issuance and use — in-memory tokens don't survive restarts; add external persistence if needed.
  3. Increase the token TTL/Count configuration if links expire too quickly for your workflow.
  4. Verify the URL contains the full, untruncated token (mail clients sometimes wrap/break long URLs).
Defensive patterns

Strategy: try-catch

Try / catch

tok, err := tmptokens.Get(token)
if errors.Is(err, tmptokens.Err) {
    http.Error(w, "This link has expired or is invalid. Please request a new one.", http.StatusNotFound)
    return
}

Prevention

When it happens

Trigger: Any handler resolving a token (GetServerConfig, GetCampaignArchivesFeed, CampaignArchivePage, getCampaignArchives, compileArchiveCampaigns) receives a token string not in the map, past TTL, or past its Count limit; server restarts wipe the in-memory map.

Common situations: User clicks an old archive/unsubscribe link after a listmonk restart (tokens are not persisted); link shared after its expiry window; exceeding the allowed views of a limited-use token; truncated/mistyped token URL.

Related errors


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