{"record":{"id":"c27efa9f69eda8a1","repo":"odysseus-dev/odysseus","slug":"cannot-create-reply-sender-address-is-missing-fro","errorCode":null,"errorMessage":"Cannot create reply: sender address is missing from this email.","messagePattern":"Cannot create reply: sender address is missing from this email\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"static/js/emailInbox.js","lineNumber":929,"sourceCode":"        toAddress = originalToWithoutMe.join(', ') || originalCcWithoutMe[0] || data.from_address;\n        ccAddresses = originalCcWithoutMe.filter(addr => !originalToWithoutMe.some(t => extractEmail(t) === extractEmail(addr))).join(', ');\n      } else {\n        // Build reply-all: TO = original sender, CC = everyone else (To + Cc minus me)\n        ccAddresses = buildReplyAllCc(data, myAddresses);\n      }\n    } else if (mode === 'forward') {\n      toAddress = '';\n      subjectPrefix = 'Fwd: ';\n    }\n\n    // Don't double-prefix `Re:` / `Fwd:` when the subject already starts with one.\n    // Replies to replies were producing `Re: Re: Re: …` which can also break\n    // some IMAP servers' header parsing on very long subject lines.\n    let _baseSubject = (data.subject || '').trim();\n    if (subjectPrefix === 'Re: ' && /^re\\s*:/i.test(_baseSubject)) subjectPrefix = '';\n    else if (subjectPrefix === 'Fwd: ' && /^fwd?\\s*:/i.test(_baseSubject)) subjectPrefix = '';\n    if (mode !== 'forward' && !String(toAddress || '').trim()) {\n      throw new Error('Cannot create reply: sender address is missing from this email.');\n    }\n    let content = `To: ${toAddress}\\nSubject: ${subjectPrefix}${_baseSubject}`;\n    if (ccAddresses) content += `\\nCc: ${ccAddresses}`;\n    if (mode !== 'forward' && data.message_id) content += `\\nIn-Reply-To: ${data.message_id}`;\n    if (mode !== 'forward' && data.message_id) content += `\\nReferences: ${data.references ? data.references + ' ' + data.message_id : data.message_id}`;\n    content += `\\nX-Source-UID: ${em.uid}`;\n    content += `\\nX-Source-Folder: ${folderAtStart}`;\n    if (data.attachments && data.attachments.length > 0) {\n      const attStr = data.attachments.map(a => `${a.index}:${a.filename}:${a.size}`).join('|');\n      content += `\\nX-Attachments: ${attStr}`;\n      if (mode === 'forward') content += `\\nX-Forward-Attachments: 1`;\n    }\n    content += '\\n---\\n';\n\n    // Format the original date in a human-readable way for the quote header\n    let niceDate = data.date || '';\n    try {\n      if (data.date) {","sourceCodeStart":911,"sourceCodeEnd":947,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/emailInbox.js#L911-L947","documentation":"A local validation throw, not a network error: when building a reply/reply-all draft, the computed To address (from the original message's sender) is empty or whitespace, so the draft would have no recipient. Forward mode is exempt because the user supplies recipients.","triggerScenarios":"Clicking Reply/Reply-All on a message whose From header lacks a routable address — e.g. 'Undisclosed recipients', a malformed From with only a display name, or parser output that produced no email address. Only mode !== 'forward' path throws.","commonSituations":"Automated/noreply mailers with non-standard headers; a provider-specific header layout the parser does not extract sender from; corrupted message metadata in the fetched JSON.","solutions":["Open the raw message source and check what the From header actually contains","For such messages, use Forward instead and type the recipient manually","If you own the parser, ensure sender extraction falls back to Reply-To then From","Improve the UX by pre-filling the compose window with an empty To instead of throwing, letting the user fill it"],"exampleFix":"// before\nif (mode !== 'forward' && !String(toAddress || '').trim()) {\n  throw new Error('Cannot create reply: sender address is missing from this email.');\n}\n\n// after — open compose with empty To instead of failing\nif (mode !== 'forward' && !String(toAddress || '').trim()) {\n  toAddress = '';\n  uiModule.showToast('No sender address found — enter a recipient');\n}","handlingStrategy":"validation","validationCode":"const senderAddr = String(toAddress || '').trim();\nif (mode !== 'forward' && !senderAddr) { openComposeWith({ to: '', subject: `${subjectPrefix}${_baseSubject}`, warn: 'No sender address on original — add a recipient' }); return; }","typeGuard":"function hasRoutableSender(data) { const v = String(data?.from_address || data?.sender || '').trim(); return /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(v); }","tryCatchPattern":"try { if (mode !== 'forward' && !hasRoutableSender(data)) throw new Error('Cannot create reply: sender address is missing from this email.'); ... } catch (e) { uiModule.showError(e.message); }","preventionTips":["Extract sender via fallback chain: Reply-To → From → Sender before giving up","Offer Forward as the automatic alternative when sender is unparseable","Log the raw From header when extraction fails to improve the parser"],"tags":["email","validation","reply","missing-sender","header-parsing"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}