gotify/server · info
message does not exist
Error message
message does not exist
What it means
An HTTP 404 raised by the DeleteMessage handler when GetMessageByID returns no message for the given path ID. Deleting a message that never existed or was already deleted yields this error. The handler also verifies the message's parent application belongs to the caller before deleting.
Source
Thrown at api/message.go:310
// description: Unauthorized
// schema:
// $ref: "#/definitions/Error"
// 403:
// description: Forbidden
// schema:
// $ref: "#/definitions/Error"
// 404:
// description: Not Found
// schema:
// $ref: "#/definitions/Error"
func (a *MessageAPI) DeleteMessage(ctx *gin.Context) {
withID(ctx, "id", func(id uint) {
msg, err := a.DB.GetMessageByID(id)
if success := successOrAbort(ctx, 500, err); !success {
return
}
if msg == nil {
ctx.AbortWithError(404, errors.New("message does not exist"))
return
}
app, err := a.DB.GetApplicationByID(msg.ApplicationID)
if success := successOrAbort(ctx, 500, err); !success {
return
}
if app != nil && app.UserID == auth.GetUserID(ctx) {
successOrAbort(ctx, 500, a.DB.DeleteMessageByID(id))
} else {
ctx.AbortWithError(404, errors.New("message does not exist"))
}
})
}
// CreateMessage creates a message, authentication via application token, client token, or basic auth is required.
// swagger:operation POST /message message createMessage
//
// Create a message.View on GitHub (pinned to 14bfc25627)
Solutions
- Treat 404 on message delete as success — the goal state (message gone) is already achieved; make delete idempotent.
- Fetch current message IDs via GET /message (application-scoped) immediately before deleting instead of reusing cached IDs.
- Check server message-retention limits: old messages may be auto-deleted before your delete call runs.
- Ensure you are not deleting a message ID from a different application/instance.
Example fix
// before
const res = await fetch(`/message/${msgId}`, { method: 'DELETE' });
if (!res.ok) throw new Error('delete failed'); // double-delete crashes the loop
// after
const res = await fetch(`/message/${msgId}`, { method: 'DELETE' });
if (res.status === 404) return; // already gone — idempotent success
if (!res.ok) throw new Error(`delete failed: ${res.status}`); Defensive patterns
Strategy: validation
Validate before calling
// Check the message still exists in the current listing before deleting by ID
const page = await fetch(`/message?token=${appToken}&limit=100`, { headers: { 'X-Gotify-Key': userToken } }).then(r => r.json());
if (!page.messages.some(m => m.id === msgId)) {
return; // already deleted or purged — skip
} Try / catch
// Idempotent delete: 404 means the message is already gone
const res = await fetch(`/message/${msgId}`, { method: 'DELETE' });
if (res.status === 404) return; // success: nothing to delete
if (!res.ok) throw new Error(`unexpected status ${res.status}`); Prevention
- Never assume a previously listed message ID still exists — retention limits purge old messages.
- In read-then-delete consumers, treat 404 as the expected second outcome, not a failure.
- Re-fetch message IDs right before deleting rather than caching across retries/restarts.
- When cleaning up by ID ranges, stop at the first 404 to confirm pruning boundaries.
When it happens
Trigger: DELETE /message/{id} where the ID (1) never existed, (2) was already deleted (double-delete), or (3) was pruned by retention/limits (GetMessagesByApplicationSince pagination or max-message settings removed old messages).
Common situations: Consumers deleting each message after reading, then retrying after a crash/timeout and re-deleting the same ID; message IDs captured earlier but purged by server-side message limits before the delete runs; off-by-one or stale list data causing an outdated ID to be used.
Related errors
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/f670c5613627bc79.
Report an issue: GitHub.