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
- Regenerate the link (re-send the campaign or re-create the archive token) so a fresh token is issued.
- Check whether the process restarted between issuance and use — in-memory tokens don't survive restarts; add external persistence if needed.
- Increase the token TTL/Count configuration if links expire too quickly for your workflow.
- 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
- Regenerate links after any listmonk restart — tokens are in-memory only.
- Set token TTL/Count generous enough for real-world link-click delays.
- Tell users to request fresh links instead of retrying old bookmarks.
- Persist tokens externally (or re-issue on boot) if long-lived links are required.
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
- 'email' column not found
- invalid e-mail address
- error reading lang file: %s: %v
- error parsing lang file: %s: %v
- error reading default i18n language file: %s: %v
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/0bf5d7033d665f07.
Report an issue: GitHub.