googleworkspace/cli · error · GwsError
Failed to serialize email: {e}
Error message
Failed to serialize email: {e} What it means
`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.
Source
Thrown at crates/google-workspace-cli/src/helpers/gmail/mod.rs:1251
mb.body(MimePart::new("multipart/mixed", mixed_parts))
}
} else {
// No inline images, or plain-text mode — all parts become regular attachments.
// Callers strip inline parts in plain-text mode (matching Gmail web), so
// only regular attachments should reach here. If any inline parts do arrive,
// they are treated as regular attachments (defense-in-depth).
let mb = if html {
mb.html_body(body_str)
} else {
mb.text_body(body_str)
};
attachments.iter().fold(mb, |mb, att| {
mb.attachment(&att.content_type, &att.filename, att.data.as_slice())
})
};
mb.write_to_string()
.map_err(|e| GwsError::Other(anyhow::anyhow!("Failed to serialize email: {e}")))
}
/// Parse an optional clap argument, trimming whitespace and treating
/// empty/whitespace-only values as None.
pub(super) fn parse_optional_trimmed(matches: &ArgMatches, name: &str) -> Option<String> {
matches
.get_one::<String>(name)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
/// Parse an optional clap argument as a comma-separated mailbox list.
/// Returns `None` when the argument is absent, empty, or yields no valid addresses.
pub(super) fn parse_optional_mailboxes(matches: &ArgMatches, name: &str) -> Option<Vec<Mailbox>> {
parse_optional_trimmed(matches, name)
.map(|s| Mailbox::parse_list(&s))
.filter(|v| !v.is_empty())
}View on GitHub (pinned to a3768d0e82)
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.
Example fix
// before: raw flag values flow straight into the MIME builder
let mb = mb.subject(subject).from(from);
// after: strip control chars and validate mailbox shape before building
fn sanitize_header_value(s: &str) -> String {
s.chars().filter(|c| !c.is_control()).collect::<String>().trim().to_string()
}
let subject = sanitize_header_value(&subject);
if !from.iter().any(|m| m.address.contains('@')) {
return Err(GwsError::Other(anyhow::anyhow!("--from must contain a valid email address")));
} Defensive patterns
Strategy: validation
Validate before calling
// Validate before building the MIME message
fn valid_mailbox_list(mbs: &[Mailbox]) -> bool {
mbs.iter().all(|m| m.address.contains('@') && !m.address.contains(|c: char| c.is_control()))
}
let subject_clean: String = subject.chars().filter(|c| !c.is_control()).collect::<String>();
if from.is_empty() || !valid_mailbox_list(&from) || !valid_mailbox_list(&to) {
return Err(anyhow::anyhow!("from/to must be valid email addresses"));
} Type guard
fn is_sanitized_header_value(s: &str) -> bool {
!s.is_empty() && !s.chars().any(|c| c.is_control())
} Try / catch
// Compose helpers return GwsError::Other on builder failure — sanitize inputs so this branch is unreachable: assert!(is_sanitized_header_value(&subject), "subject contains control characters"); let mime = build_mime(...)?; // write_to_string error now impossible for clean inputs
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Message is missing From header
- Failed to fetch sendAs settings: {e}
- Message is missing Message-ID header
- Failed to fetch message: {e}
- Failed to parse message: {e}
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/9b052f97dc34b132.
Report an issue: GitHub.