diff --git a/.changeset/gmail-header-case.md b/.changeset/gmail-header-case.md new file mode 100644 index 000000000..e078d42b2 --- /dev/null +++ b/.changeset/gmail-header-case.md @@ -0,0 +1,5 @@ +--- +"@googleworkspace/cli": patch +--- + +Match Gmail message header names case-insensitively, so `CC` from Exchange/Outlook senders is no longer dropped by `gmail +reply-all` and `gmail +read` diff --git a/crates/google-workspace-cli/src/helpers/gmail/mod.rs b/crates/google-workspace-cli/src/helpers/gmail/mod.rs index caeb8b6b0..c001dd029 100644 --- a/crates/google-workspace-cli/src/helpers/gmail/mod.rs +++ b/crates/google-workspace-cli/src/helpers/gmail/mod.rs @@ -258,15 +258,18 @@ fn parse_message_headers(headers: &[Value]) -> ParsedMessageHeaders { let name = header.get("name").and_then(|v| v.as_str()).unwrap_or(""); let value = header.get("value").and_then(|v| v.as_str()).unwrap_or(""); - match name { - "From" => parsed.from = value.to_string(), - "Reply-To" => append_address_list_header_value(&mut parsed.reply_to, value), - "To" => append_address_list_header_value(&mut parsed.to, value), - "Cc" => append_address_list_header_value(&mut parsed.cc, value), - "Subject" => parsed.subject = value.to_string(), - "Date" => parsed.date = value.to_string(), - "Message-ID" | "Message-Id" => parsed.message_id = value.to_string(), - "References" => append_header_value(&mut parsed.references, value), + // Header names are case-insensitive, and the API hands through whatever casing + // the sending MTA used. Exchange/Outlook emit "CC"/"MESSAGE-ID", which used to + // fall through the match below and get dropped. + match name.to_ascii_lowercase().as_str() { + "from" => parsed.from = value.to_string(), + "reply-to" => append_address_list_header_value(&mut parsed.reply_to, value), + "to" => append_address_list_header_value(&mut parsed.to, value), + "cc" => append_address_list_header_value(&mut parsed.cc, value), + "subject" => parsed.subject = value.to_string(), + "date" => parsed.date = value.to_string(), + "message-id" => parsed.message_id = value.to_string(), + "references" => append_header_value(&mut parsed.references, value), _ => {} } } @@ -2427,6 +2430,50 @@ mod tests { assert_eq!(original.body_html.as_deref(), Some("
HTML only
")); } + #[test] + fn test_parse_original_message_accepts_non_canonical_header_casing() { + // Exchange/Outlook style casing: the old case-sensitive match dropped CC and + // blew up on the missing From header. + let msg = json!({ + "threadId": "thread-789", + "snippet": "", + "payload": { + "mimeType": "text/plain", + "headers": [ + { "name": "FROM", "value": "alice@example.com" }, + { "name": "TO", "value": "bob@example.com" }, + { "name": "cc", "value": "carol@example.com" }, + { "name": "CC", "value": "dave@example.com" }, + { "name": "Subject", "value": "Hello" }, + { "name": "DATE", "value": "Fri, 6 Mar 2026 12:00:00 +0000" }, + { "name": "message-id", "value": "