Skip to content

feat(gmail): add +read helper for extracting message body as plain text#465

Open
anshul-garg27 wants to merge 3 commits intogoogleworkspace:mainfrom
anshul-garg27:feat/gmail-read-helper
Open

feat(gmail): add +read helper for extracting message body as plain text#465
anshul-garg27 wants to merge 3 commits intogoogleworkspace:mainfrom
anshul-garg27:feat/gmail-read-helper

Conversation

@anshul-garg27
Copy link
Contributor

Closes #438.

Summary

Add gws gmail +read helper that fetches a Gmail message and outputs its headers and body as structured data or plain text.

Usage

# Full message with headers
gws gmail +read --message-id 18f1a2b3c4d

# Just the body text (pipe-friendly)
gws gmail +read --message-id 18f1a2b3c4d --body-only

# HTML body
gws gmail +read --message-id 18f1a2b3c4d --html

# JSON for scripting
gws gmail +read --message-id 18f1a2b3c4d --format json | jq '.body'

Changes

  • New file: src/helpers/gmail/read.rshandle_read() fetches message via Gmail API, reuses parse_original_message() for header/body extraction
  • src/helpers/gmail/mod.rs — register +read subcommand and handler
  • Tests for argument parsing (basic, --html, --body-only, empty message-id validation)

Test plan

  • cargo test — 588 passed, 0 failed
  • cargo clippy -- -D warnings — clean
  • Manual: gws gmail +read --message-id <id> → verify output

Generated with Claude Code

@changeset-bot
Copy link

changeset-bot bot commented Mar 13, 2026

🦋 Changeset detected

Latest commit: 93d39dd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@googleworkspace-bot googleworkspace-bot added the area: core Core CLI parsing, commands, error handling, utilities label Mar 13, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the gws tool by introducing a new +read helper within the gmail subcommand. This feature provides users with a powerful and flexible way to programmatically access and inspect Gmail message content directly from the command line, offering granular control over the output format and specific message parts to be extracted.

Highlights

  • New +read subcommand for Gmail: Introduced a new +read subcommand under gws gmail to fetch and display the content of Gmail messages.
  • Flexible Output Options: Implemented options to retrieve messages as plain text or HTML, output only the message body, and support various output formats including JSON, table, YAML, and CSV.
  • Dedicated Module for Read Logic: Added a new module src/helpers/gmail/read.rs to encapsulate the logic for fetching, parsing, and formatting Gmail messages, along with argument parsing and unit tests.
Changelog
  • src/helpers/gmail/mod.rs
    • Imported the new read module and its handle_read function.
    • Registered the +read subcommand with clap, defining its arguments (--message-id, --html, --body-only, --format) and comprehensive help text.
    • Integrated handle_read into the main command dispatch logic to process the new subcommand.
  • src/helpers/gmail/read.rs
    • Added a new file containing the core logic for the +read subcommand.
    • Implemented handle_read to fetch Gmail messages using the Gmail API, parse their content, and format the output based on user-specified options.
    • Defined the ReadConfig struct to encapsulate parsed command-line arguments for the +read subcommand.
    • Included the parse_read_args function for robust argument validation and configuration.
    • Provided unit tests for argument parsing, covering basic usage, HTML/body-only flags, and validation for empty message IDs.
Activity
  • cargo test was executed, with all 588 tests passing successfully.
  • cargo clippy -- -D warnings was run, indicating a clean code base with no warnings.
  • Manual testing for the gws gmail +read --message-id <id> command is pending.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new gws gmail +read helper to fetch and display Gmail messages, which is a useful addition. The implementation is mostly solid, with good argument parsing and tests. However, there is significant code duplication for fetching the message data, which has already been implemented in the fetch_message_metadata helper function. Reusing this existing function will not only simplify the code but also add resilience by incorporating the existing retry logic that is currently missing in the new implementation.

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a useful gws gmail +read helper for fetching and displaying Gmail messages. The implementation is solid, but I've identified an opportunity to improve performance by reducing string allocations when constructing the output. My suggestion refactors the logic to move data instead of cloning it, which is more idiomatic and efficient in Rust.

@anshul-garg27
Copy link
Contributor Author

Ready for review — all tests passing, Gemini feedback addressed. @jpoehnelt

@googleworkspace-bot
Copy link
Collaborator

/gemini review

Closes googleworkspace#438.

Add `gws gmail +read` helper that fetches a Gmail message and extracts
its body as plain text (or HTML with --html). Supports:

- --message-id: Gmail message ID to read (required)
- --html: return HTML body instead of plain text
- --body-only: print just the body without headers/metadata
- --format: output format (json, table, yaml, csv)

Reuses the existing parse_original_message() infrastructure from
reply/forward to extract headers and body content, including
base64-decoded MIME parts.
…ogic

Address Gemini review: replace manual HTTP fetch + parse with the
existing fetch_message_metadata() helper which also uses send_with_retry
for transient error resilience.
@anshul-garg27 anshul-garg27 force-pushed the feat/gmail-read-helper branch from c0e85e1 to 93d39dd Compare March 13, 2026 20:33
@googleworkspace-bot
Copy link
Collaborator

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new gws gmail +read helper, which is a great addition for fetching and inspecting Gmail messages. The implementation is clean and reuses existing helpers effectively. I've found one high-severity issue related to input handling that could lead to unexpected API failures. My feedback includes a code suggestion to address this.

Comment on lines +59 to +65
let message_id = matches.get_one::<String>("message-id").unwrap().to_string();

if message_id.trim().is_empty() {
return Err(GwsError::Validation(
"--message-id must not be empty".to_string(),
));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The message-id is not trimmed before being used. If a user provides a message ID with leading or trailing whitespace (e.g., from a copy-paste), the validation will pass, but the API call will likely fail because the whitespace will be URL-encoded as part of the ID. The message-id should be trimmed before validation and use to prevent these unnecessary failures and improve usability.

Suggested change
let message_id = matches.get_one::<String>("message-id").unwrap().to_string();
if message_id.trim().is_empty() {
return Err(GwsError::Validation(
"--message-id must not be empty".to_string(),
));
}
let message_id = matches.get_one::<String>("message-id").unwrap().trim().to_string();
if message_id.is_empty() {
return Err(GwsError::Validation(
"--message-id must not be empty".to_string(),
));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, added a .trim() on the message-id before validation

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a +read helper for Gmail, which is a great addition. The implementation is mostly solid, but I've identified a couple of areas for improvement. One is to make argument parsing for the output format more robust using clap's value_parser. The other is a refactoring opportunity in handle_read to improve efficiency by avoiding several unnecessary string allocations. These changes will make the new helper more robust and performant.

Comment on lines +758 to +761
Arg::new("format")
.long("format")
.help("Output format: json (default), table, yaml, csv")
.value_name("FORMAT"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For better robustness, it's a good practice to use value_parser to validate the format argument against the allowed values. This ensures that clap will reject any invalid format string before it reaches your handler code, preventing potential panics or unexpected behavior from the OutputFormat::from_str call.

Suggested change
Arg::new("format")
.long("format")
.help("Output format: json (default), table, yaml, csv")
.value_name("FORMAT"),
Arg::new("format")
.long("format")
.help("Output format: json (default), table, yaml, csv")
.value_name("FORMAT")
.value_parser(["json", "table", "yaml", "csv"]),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, added value_parser for the format arg so clap rejects bad values upfront

Comment on lines +24 to +46
let body = if config.html {
parsed
.body_html
.clone()
.unwrap_or_else(|| parsed.body_text.clone())
} else {
parsed.body_text.clone()
};

if config.body_only {
println!("{body}");
} else {
let output = json!({
"id": config.message_id,
"from": parsed.from,
"to": parsed.to,
"cc": parsed.cc,
"subject": parsed.subject,
"date": parsed.date,
"body": body,
});
println!("{}", crate::formatter::format_value(&output, &fmt));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This block can be refactored to be more efficient by avoiding several string clones. By using references (&str) for the body content and for the fields passed to the json! macro, you can prevent unnecessary memory allocations. This also makes the code more idiomatic.

    let body_ref = if config.html {
        parsed.body_html.as_deref().unwrap_or(&parsed.body_text)
    } else {
        &parsed.body_text
    };

    if config.body_only {
        println!("{body_ref}");
    } else {
        let output = json!({
            "id": &config.message_id,
            "from": &parsed.from,
            "to": &parsed.to,
            "cc": &parsed.cc,
            "subject": &parsed.subject,
            "date": &parsed.date,
            "body": body_ref,
        });
        println!("{}", crate::formatter::format_value(&output, &fmt));
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point on the clones, cleaned it up to use references where possible

weiquarter pushed a commit to weiquarter/cli that referenced this pull request Mar 15, 2026
Encapsulates the new `gws gmail +read` subcommand (introduced in googleworkspace#465)
as an agent skill. The skill documents how to extract decoded plain-text
message bodies from Gmail without dealing with raw MIME payloads, and
updates the gws-gmail skill to list +read in its helper commands table.

Closes googleworkspace#438

https://claude.ai/code/session_01PkDwmaayDdUhCDgurYesXC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Core CLI parsing, commands, error handling, utilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Gmail +read helper for extracting message body as plain text

2 participants