{"record":{"id":"c54b6725ccc740a9","repo":"odysseus-dev/odysseus","slug":"unsubscribe-failed","errorCode":null,"errorMessage":"Unsubscribe failed","messagePattern":"Unsubscribe failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/emailLibrary.js","lineNumber":1759,"sourceCode":"        btn.disabled = true;\n        btn.textContent = 'Sending…';\n        try {\n          const r = await fetch(emailApiUrl('/api/email/unsubscribe/execute', {\n            account_id: state._libAccountId || undefined,\n          }), {\n            method: 'POST',\n            credentials: 'same-origin',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({\n              uid: c.uid,\n              folder: c.folder || state._libFolder || 'INBOX',\n              account_id: state._libAccountId || '',\n              method_index: 0,\n              move_to_spam: false,\n            }),\n          });\n          const d = await r.json().catch(() => ({}));\n          if (!d.success) throw new Error(d.error || 'Unsubscribe failed');\n          btn.textContent = 'Sent';\n          _markUnsubscribeCardDone(modal, idx);\n          showToast('Unsubscribe email sent');\n          _showUnsubscribeCleanupPrompt(modal, [c]);\n        } catch (err) {\n          console.error(err);\n          btn.disabled = false;\n          btn.textContent = _unsubscribeMethodLabel(c.recommended_method);\n          showToast(err?.message || 'Unsubscribe failed');\n        }\n      });\n    });\n  } catch (err) {\n    console.error(err);\n    finishScanStatus?.('Failed to scan email headers');\n  }\n}\n","sourceCodeStart":1741,"sourceCodeEnd":1777,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/emailLibrary.js#L1741-L1777","documentation":"Thrown when POST of the unsubscribe request (method_index 0 = reply-based List-Unsubscribe) returns success=false. The button re-enables and restores its method label, and the toast shows the server's error or the generic 'Unsubscribe failed'.","triggerScenarios":"Clicking Send on an unsubscribe card: the message's List-Unsubscribe mailto can't be parsed or sent (missing/invalid header, SMTP relay refuses the sender), the UID/folder pair no longer resolves, or the account's outgoing credentials are missing.","commonSituations":"mailto unsubscribe address missing from the header; provider blocks programmatic sends to that address; message moved/expunged so uid lookup fails; account configured for receive-only without SMTP.","solutions":["Read d.error in the response body (Network tab) — it names whether parsing or sending failed","Verify the account has working outgoing/SMTP credentials","Use the cleanup fallback (mark as spam / delete) when the sender's unsubscribe flow is broken","Refresh the list and retry if the UID was stale"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if (!c.list_unsubscribe && !c.recommended_method) { showToast('No unsubscribe method available — use mark-as-spam'); return; }","typeGuard":null,"tryCatchPattern":"try { const d = await r.json().catch(() => ({})); if (!d.success) throw new Error(d.error || 'Unsubscribe failed'); ... } catch (err) { btn.disabled = false; btn.textContent = _unsubscribeMethodLabel(c.recommended_method); showToast(err?.message || 'Unsubscribe failed'); }","preventionTips":["Always keep the spam/delete cleanup path as the fallback when unsubscribe sending fails (as the follow-up prompt does)","Verify SMTP credentials for the account before offering send-based unsubscribe","Restore the button label in catch so retries are possible","Parse List-Unsubscribe headers defensively — mailto: is frequently absent/malformed"],"tags":["email","unsubscribe","smtp","list-unsubscribe","fetch"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}