{"record":{"id":"004351f65fa639f2","repo":"googleworkspace/cli","slug":"message-is-missing-from-header","errorCode":null,"errorMessage":"Message is missing From header","messagePattern":"Message is missing From header","errorType":"validation","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/gmail/mod.rs","lineNumber":316,"sourceCode":"        .and_then(|v| v.as_str())\n        .filter(|s| !s.is_empty())\n        .map(String::from);\n\n    let snippet = msg\n        .get(\"snippet\")\n        .and_then(|v| v.as_str())\n        .unwrap_or(\"\")\n        .to_string();\n\n    let parsed_headers = msg\n        .get(\"payload\")\n        .and_then(|p| p.get(\"headers\"))\n        .and_then(|h| h.as_array())\n        .map(|headers| parse_message_headers(headers))\n        .unwrap_or_default();\n\n    if parsed_headers.from.is_empty() {\n        return Err(GwsError::Other(anyhow::anyhow!(\n            \"Message is missing From header\"\n        )));\n    }\n\n    let message_id = strip_angle_brackets(&parsed_headers.message_id);\n    if message_id.is_empty() {\n        return Err(GwsError::Other(anyhow::anyhow!(\n            \"Message is missing Message-ID header\"\n        )));\n    }\n\n    let PayloadContents {\n        body_text: extracted_text,\n        body_html,\n        parts: original_parts,\n    } = msg\n        .get(\"payload\")\n        .map(extract_payload_contents)","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/gmail/mod.rs#L298-L334","documentation":"Raised by `parse_original_message` (used by `+reply`/`+reply-all`/`+forward`) after fetching a Gmail message with `format=full`: the `payload.headers` array was parsed but contained no `From` header, or `payload`/`headers` was absent so `parse_message_headers` fell back to empty defaults. The helper refuses to operate on a message it cannot attribute, because a reply needs a sender to address.","triggerScenarios":"Replying/forwarding a message whose raw MIME lacked a From header (rare bounce/NDR or spam synthesized by legacy MTAs); fetching a draft or partial message object; a Gmail API response where headers live under a nested part rather than the top-level payload; passing a message ID that returns a minimal object.","commonSituations":"Users hitting `gws gmail +reply` on automated notifications, bounce messages, or imported mail from broken gateways; scripts that feed message IDs obtained from a different format (`format=metadata` vs `format=full`).","solutions":["Inspect the message first: `gws gmail users.messages get --userId me --id <ID> --params '{\"format\":\"full\"}' | jq '.payload.headers'` to confirm From is really absent.","If the message legitimately lacks From, reply is impossible — report the ID to the sender/gateway owner or pick another message.","If headers exist but are nested, this indicates an API/shape change; check the Gmail API release notes and update the CLI (`cargo install` latest / bump version).","For scripting, pre-filter message IDs through a metadata list query that excludes malformed messages."],"exampleFix":"// before: any message without From aborts the helper\nif parsed_headers.from.is_empty() {\n    return Err(GwsError::Other(anyhow::anyhow!(\"Message is missing From header\")));\n}\n\n// after: fall back to the raw-size sender only if present, else a clear per-message error\nif parsed_headers.from.is_empty() {\n    return Err(GwsError::Other(anyhow::anyhow!(\n        \"Message {} has no From header; cannot determine reply recipient (malformed or synthesized message)\",\n        message_id\n    )));\n}","handlingStrategy":"type-guard","validationCode":"// Before replying, fetch the message once and check header presence\nlet msg: Value = fetch_message(...).await?;\nif !has_from_header(&msg) {\n    eprintln!(\"Skipping {}: no From header\", message_id);\n    return Ok(());\n}","typeGuard":"fn has_from_header(msg: &serde_json::Value) -> bool {\n    msg.get(\"payload\")\n        .and_then(|p| p.get(\"headers\"))\n        .and_then(|h| h.as_array())\n        .map(|hs| hs.iter().any(|h| h.get(\"name\").and_then(|n| n.as_str()).map(|n| n.eq_ignore_ascii_case(\"from\")).unwrap_or(false) && h.get(\"value\").and_then(|v| v.as_str()).is_some_and(|v| !v.trim().is_empty())))\n        .unwrap_or(false)\n}","tryCatchPattern":"// When calling the reply helper, match on GwsError and skip malformed messages in batch flows:\nmatch run_reply(message_id).await {\n    Ok(_) => {},\n    Err(GwsError::Other(e)) if e.to_string().contains(\"missing From header\") => {\n        eprintln!(\"skipping malformed message {message_id}\");\n        continue;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["In batch reply scripts, pre-filter candidates with a metadata fetch and the has_from_header guard.","Treat From-less messages as data-quality issues at the source (fix the sending MTA).","Pin the fetch to format=full — other formats omit payload headers."],"tags":["gmail","email-headers","reply","mime","schema-validation"],"backgroundTag":"missing-email-header","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}