{"record":{"id":"f670c5613627bc79","repo":"gotify/server","slug":"message-does-not-exist","errorCode":null,"errorMessage":"message does not exist","messagePattern":"message does not exist","errorType":"http","errorClass":null,"httpStatus":404,"severity":"info","filePath":"api/message.go","lineNumber":310,"sourceCode":"//\t    description: Unauthorized\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  403:\n//\t    description: Forbidden\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  404:\n//\t    description: Not Found\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\nfunc (a *MessageAPI) DeleteMessage(ctx *gin.Context) {\n\twithID(ctx, \"id\", func(id uint) {\n\t\tmsg, err := a.DB.GetMessageByID(id)\n\t\tif success := successOrAbort(ctx, 500, err); !success {\n\t\t\treturn\n\t\t}\n\t\tif msg == nil {\n\t\t\tctx.AbortWithError(404, errors.New(\"message does not exist\"))\n\t\t\treturn\n\t\t}\n\t\tapp, err := a.DB.GetApplicationByID(msg.ApplicationID)\n\t\tif success := successOrAbort(ctx, 500, err); !success {\n\t\t\treturn\n\t\t}\n\t\tif app != nil && app.UserID == auth.GetUserID(ctx) {\n\t\t\tsuccessOrAbort(ctx, 500, a.DB.DeleteMessageByID(id))\n\t\t} else {\n\t\t\tctx.AbortWithError(404, errors.New(\"message does not exist\"))\n\t\t}\n\t})\n}\n\n// CreateMessage creates a message, authentication via application token, client token, or basic auth is required.\n// swagger:operation POST /message message createMessage\n//\n// Create a message.","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/message.go#L292-L328","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst res = await fetch(`/message/${msgId}`, { method: 'DELETE' });\nif (!res.ok) throw new Error('delete failed'); // double-delete crashes the loop\n\n// after\nconst res = await fetch(`/message/${msgId}`, { method: 'DELETE' });\nif (res.status === 404) return; // already gone — idempotent success\nif (!res.ok) throw new Error(`delete failed: ${res.status}`);","handlingStrategy":"validation","validationCode":"// Check the message still exists in the current listing before deleting by ID\nconst page = await fetch(`/message?token=${appToken}&limit=100`, { headers: { 'X-Gotify-Key': userToken } }).then(r => r.json());\nif (!page.messages.some(m => m.id === msgId)) {\n  return; // already deleted or purged — skip\n}","typeGuard":null,"tryCatchPattern":"// Idempotent delete: 404 means the message is already gone\nconst res = await fetch(`/message/${msgId}`, { method: 'DELETE' });\nif (res.status === 404) return;          // success: nothing to delete\nif (!res.ok) throw new Error(`unexpected status ${res.status}`);","preventionTips":["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."],"tags":["http-404","gotify","rest-api","idempotency","message-delete"],"backgroundTag":"http-404-resource-not-found","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}