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

  1. Sanitize inputs: strip control characters from subject/body and validate addresses before sending (`user@host` or `Name <user@host>` with balanced brackets).
  2. Check that at least one recipient and a From are present after parsing.
  3. If body text comes from a file/subprocess, ensure it is UTF-8 text, not binary.
  4. 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

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


AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16). Data as JSON: /api/errors/9b052f97dc34b132. Report an issue: GitHub.