{"record":{"id":"9b052f97dc34b132","repo":"googleworkspace/cli","slug":"failed-to-serialize-email-e","errorCode":null,"errorMessage":"Failed to serialize email: {e}","messagePattern":"Failed to serialize email: (.+?)","errorType":"exception","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/gmail/mod.rs","lineNumber":1251,"sourceCode":"            mb.body(MimePart::new(\"multipart/mixed\", mixed_parts))\n        }\n    } else {\n        // No inline images, or plain-text mode — all parts become regular attachments.\n        // Callers strip inline parts in plain-text mode (matching Gmail web), so\n        // only regular attachments should reach here. If any inline parts do arrive,\n        // they are treated as regular attachments (defense-in-depth).\n        let mb = if html {\n            mb.html_body(body_str)\n        } else {\n            mb.text_body(body_str)\n        };\n        attachments.iter().fold(mb, |mb, att| {\n            mb.attachment(&att.content_type, &att.filename, att.data.as_slice())\n        })\n    };\n\n    mb.write_to_string()\n        .map_err(|e| GwsError::Other(anyhow::anyhow!(\"Failed to serialize email: {e}\")))\n}\n\n/// Parse an optional clap argument, trimming whitespace and treating\n/// empty/whitespace-only values as None.\npub(super) fn parse_optional_trimmed(matches: &ArgMatches, name: &str) -> Option<String> {\n    matches\n        .get_one::<String>(name)\n        .map(|s| s.trim().to_string())\n        .filter(|s| !s.is_empty())\n}\n\n/// Parse an optional clap argument as a comma-separated mailbox list.\n/// Returns `None` when the argument is absent, empty, or yields no valid addresses.\npub(super) fn parse_optional_mailboxes(matches: &ArgMatches, name: &str) -> Option<Vec<Mailbox>> {\n    parse_optional_trimmed(matches, name)\n        .map(|s| Mailbox::parse_list(&s))\n        .filter(|v| !v.is_empty())\n}","sourceCodeStart":1233,"sourceCodeEnd":1269,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/gmail/mod.rs#L1233-L1269","documentation":"`mb.write_to_string()` from the `mail-builder` crate (0.4.x) failed while serializing the outgoing MIME message in the gmail compose/send helper. mail-builder returns errors for structurally invalid messages: missing required From, a mailbox with an unencodable address, header values containing control characters/NUL that cannot be encoded, or similarly malformed input derived from CLI flags.","triggerScenarios":"Passing `--from`/`--to` with a malformed address that survived earlier parsing; a subject or body containing raw control characters (e.g. from a script piping binary into `--body`); an empty recipient set after filtering; non-UTF8 boundaries in filenames of `--attach` files.","commonSituations":"Scripted sends where the body comes from command substitution that injected escape sequences; users pasting addresses like `Name <user@host` (unclosed angle bracket); attachments whose filenames contain newlines.","solutions":["Sanitize inputs: strip control characters from subject/body and validate addresses before sending (`user@host` or `Name <user@host>` with balanced brackets).","Check that at least one recipient and a From are present after parsing.","If body text comes from a file/subprocess, ensure it is UTF-8 text, not binary.","Reproduce with a minimal `--body 'test'` send to isolate which flag introduces the invalid value."],"exampleFix":"// before: raw flag values flow straight into the MIME builder\nlet mb = mb.subject(subject).from(from);\n\n// after: strip control chars and validate mailbox shape before building\nfn sanitize_header_value(s: &str) -> String {\n    s.chars().filter(|c| !c.is_control()).collect::<String>().trim().to_string()\n}\nlet subject = sanitize_header_value(&subject);\nif !from.iter().any(|m| m.address.contains('@')) {\n    return Err(GwsError::Other(anyhow::anyhow!(\"--from must contain a valid email address\")));\n}","handlingStrategy":"validation","validationCode":"// Validate before building the MIME message\nfn valid_mailbox_list(mbs: &[Mailbox]) -> bool {\n    mbs.iter().all(|m| m.address.contains('@') && !m.address.contains(|c: char| c.is_control()))\n}\nlet subject_clean: String = subject.chars().filter(|c| !c.is_control()).collect::<String>();\nif from.is_empty() || !valid_mailbox_list(&from) || !valid_mailbox_list(&to) {\n    return Err(anyhow::anyhow!(\"from/to must be valid email addresses\"));\n}","typeGuard":"fn is_sanitized_header_value(s: &str) -> bool {\n    !s.is_empty() && !s.chars().any(|c| c.is_control())\n}","tryCatchPattern":"// Compose helpers return GwsError::Other on builder failure — sanitize inputs so this branch is unreachable:\nassert!(is_sanitized_header_value(&subject), \"subject contains control characters\");\nlet mime = build_mime(...)?; // write_to_string error now impossible for clean inputs","preventionTips":["Strip control characters from every value destined for a header (subject, names, filenames).","Validate mailbox addresses (contains '@', no control chars, balanced angle brackets) before compose.","Never pipe binary into --body; ensure body text is UTF-8.","Reproduce failures with a minimal `--body 'test'` send to isolate the offending flag."],"tags":["gmail","mime","mail-builder","compose","input-sanitization"],"backgroundTag":"email-build-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}