| Crates.io | cull-gmail |
| lib.rs | cull-gmail |
| version | 0.0.16 |
| created_at | 2025-09-30 06:33:50.020499+00 |
| updated_at | 2025-10-30 22:38:49.750199+00 |
| description | Cull emails from a gmail account using the gmail API |
| homepage | |
| repository | https://github.com/jerus-org/cull-gmail |
| max_upload_size | |
| id | 1860672 |
| size | 588,529 |
The cull-gmail provides a software library and command line program to enable the culling of emails using the Gmail API.
Get started with cull-gmail in minutes using the built-in setup command:
# Interactive setup (recommended)
cull-gmail init --interactive --credential-file ~/Downloads/client_secret.json
# Or preview first
cull-gmail init --dry-run
cull-gmail labels
An optional, ignored integration test exercises the Gmail API end-to-end (networked). It is ignored by default and will not run in CI.
Steps to run locally:
ClientConfig::builder() usage in docs).cargo test --test gmail_message_list_integration -- --ignored
Notes:
A command-line program for managing Gmail messages using the Gmail API. The tool provides subcommands for label querying, message querying, rule configuration, and rule execution to trash/delete messages with built-in safety features like dry-run mode.
cargo install cull-gmail
git clone https://github.com/jerus-org/cull-gmail.git
cd cull-gmail
cargo install --path .
cull-gmail --version
Create the configuration directory:
mkdir -p ~/.cull-gmail
Copy your OAuth2 credential file:
cp ~/Downloads/client_secret_*.json ~/.cull-gmail/client_secret.json
Create configuration file ~/.cull-gmail/cull-gmail.toml:
credential_file = "client_secret.json"
config_root = "~/.cull-gmail"
rules = "rules.toml"
execute = false # Start in dry-run mode
Run any command to trigger the OAuth flow:
cull-gmail labels
This will:
~/.cull-gmail/gmail1/Location: ~/.cull-gmail/cull-gmail.toml
# OAuth2 credential file (relative to config_root)
credential_file = "client_secret.json"
# Configuration directory
config_root = "~/.cull-gmail"
# Rules file
rules = "rules.toml"
# Default execution mode (false = dry-run, true = execute)
execute = false
# Alternative: Direct OAuth2 configuration
# client_id = "your-client-id.apps.googleusercontent.com"
# client_secret = "your-client-secret"
# token_uri = "https://oauth2.googleapis.com/token"
# auth_uri = "https://accounts.google.com/o/oauth2/auth"
Override any configuration setting:
export APP_CREDENTIAL_FILE="client_secret.json"
export APP_EXECUTE="true"
export APP_CLIENT_ID="your-client-id"
export APP_CLIENT_SECRET="your-client-secret"
export APP_CONFIG_ROOT="/custom/config/path"
cull-gmail [OPTIONS] [COMMAND]
-v, --verbose...: Increase logging verbosity (can be used multiple times)-q, --quiet...: Decrease logging verbosity-h, --help: Show help-V, --version: Show versionlabels: List available Gmail labelsmessages: Query and operate on messagesrules: Configure and run retention rulesList all labels in your Gmail account:
cull-gmail labels
Example Output:
INBOX: INBOX
IMPORTANT: IMPORTANT
CHAT: CHAT
SENT: SENT
DRAFT: DRAFT
promotions: Label_1234567890
old-emails: Label_0987654321
Query and operate on Gmail messages.
cull-gmail messages [OPTIONS] <ACTION>
-l, --labels <LABELS>: Filter by labels (can be used multiple times)-m, --max-results <MAX_RESULTS>: Maximum results per page [default: 200]-p, --pages <PAGES>: Maximum number of pages (0=all) [default: 1]-Q, --query <QUERY>: Gmail query stringlist: Display message informationtrash: Move messages to trashdelete: Permanently delete messagesList recent messages:
cull-gmail messages -m 10 list
List promotional emails older than 6 months:
cull-gmail messages -Q "label:promotions older_than:6m" list
Move old promotional emails to trash:
cull-gmail messages -Q "label:promotions older_than:1y" trash
Permanently delete very old messages:
cull-gmail messages -Q "older_than:5y -label:important" delete
Query with multiple labels:
cull-gmail messages -l "promotions" -l "newsletters" -Q "older_than:3m" list
Process all pages (not just first page):
cull-gmail messages -p 0 -Q "older_than:2y" list
Manage retention rules for automated email lifecycle management.
cull-gmail rules <SUBCOMMAND>
config: Configure retention rulesrun: Execute configured rulesConfigure retention rules:
cull-gmail rules config <ACTION>
rules: Manage rule definitionslabel: Add/remove labels from rulesaction: Set action (trash/delete) on rulesExample Rules Configuration:
Create/edit ~/.cull-gmail/rules.toml:
[rules."1"]
id = 1
retention = { age = "y:1", generate_label = true }
labels = ["old-emails"]
action = "Trash"
[rules."2"]
id = 2
retention = { age = "m:6", generate_label = true }
labels = ["promotions", "newsletters"]
action = "Trash"
[rules."3"]
id = 3
retention = { age = "y:5", generate_label = true }
labels = ["archive"]
action = "Delete"
Execute configured rules:
cull-gmail rules run [OPTIONS]
-e, --execute: Actually perform actions (without this, runs in dry-run mode)-t, --skip-trash: Skip rules with "trash" action-d, --skip-delete: Skip rules with "delete" actionDry-run all rules (safe, no changes made):
cull-gmail rules run
Execute all rules:
cull-gmail rules run --execute
Execute only delete rules:
cull-gmail rules run --execute --skip-trash
Execute only trash rules:
cull-gmail rules run --execute --skip-delete
The -Q, --query option supports Gmail's powerful search syntax:
# Relative dates
-Q "older_than:1y" # Older than 1 year
-Q "newer_than:30d" # Newer than 30 days
-Q "older_than:6m" # Older than 6 months
# Absolute dates
-Q "after:2023/1/1" # After January 1, 2023
-Q "before:2023/12/31" # Before December 31, 2023
# Has label
-Q "label:promotions"
-Q "label:important"
# Does NOT have label (note the minus sign)
-Q "-label:important"
-Q "-label:spam"
# Subject line
-Q "subject:newsletter"
-Q "subject:(unsubscribe OR newsletter)"
# From/To
-Q "from:noreply@example.com"
-Q "to:me@example.com"
# Message content
-Q "unsubscribe"
-Q "has:attachment"
# Read status
-Q "is:unread"
-Q "is:read"
# Star status
-Q "is:starred"
-Q "-is:starred"
# Size
-Q "size:larger_than:10M"
-Q "size:smaller_than:1M"
# Complex combinations
-Q "label:promotions older_than:6m -is:starred"
-Q "from:newsletters@example.com older_than:1y has:attachment"
-Q "subject:newsletter OR subject:promo older_than:3m"
# Step 1: Preview what will be affected
cull-gmail messages -Q "label:promotions older_than:6m" list
# Step 2: Move to trash (can be recovered for 30 days)
cull-gmail messages -Q "label:promotions older_than:6m" trash
# Archive conversations older than 2 years (excluding starred)
cull-gmail messages -Q "older_than:2y -is:starred -label:important" trash
# Permanently delete messages older than 5 years (be careful!)
cull-gmail messages -Q "older_than:5y -is:starred -label:important" delete
# Set up rules in ~/.cull-gmail/rules.toml, then:
# Preview what rules will do
cull-gmail rules run
# Execute rules
cull-gmail rules run --execute
Add to your crontab for weekly cleanup:
# Edit crontab
crontab -e
# Add this line (runs every Sunday at 2 AM)
0 2 * * 0 /home/user/.cargo/bin/cull-gmail rules run --execute >> /var/log/cull-gmail.log 2>&1
list action to preview what would be affected--execute flag to see what would happen-v for verbose logging to see exactly what's happening# Set log level
export RUST_LOG=cull_gmail=debug
# Enable all logging
export RUST_LOG=debug
# Quiet (errors only)
cull-gmail -q messages list
# Normal (default)
cull-gmail messages list
# Verbose (info level)
cull-gmail -v messages list
# Very verbose (debug level)
cull-gmail -vv messages list
# Maximum verbosity (trace level)
cull-gmail -vvv messages list
Problem: "Authentication failed" or "Invalid credentials"
Solutions:
rm -rf ~/.cull-gmail/gmail1cull-gmail labelsProblem: "Access denied" or "Insufficient permissions"
Solutions:
Problem: "No messages found" when you expect results
Solutions:
cull-gmail labels-v flag to see the actual query being sentProblem: Query returns unexpected results
Solutions:
messages list to preview before trash/deleteProblem: Operations are slow or timeout
Solutions:
-m 100-p 5 instead of -p 0Problem: "Configuration not found" or "Config parse error"
Solutions:
~/.cull-gmail/cull-gmail.toml# List all labels
cull-gmail labels
# List first 50 messages
cull-gmail messages -m 50 list
# List promotional emails from last year
cull-gmail messages -Q "label:promotions after:2023/1/1" list
# Move old promotional emails to trash
cull-gmail messages -Q "label:promotions older_than:1y" trash
# Permanently delete very old messages (careful!)
cull-gmail messages -Q "older_than:5y -is:starred" delete
# Preview all rules
cull-gmail rules run
# Execute only trash rules
cull-gmail rules run --execute --skip-delete
# Execute all rules
cull-gmail rules run --execute
The cull-gmail library provides a Rust API for managing Gmail messages through the Gmail API. It enables programmatic email culling operations including authentication, message querying, filtering, and batch operations (trash/delete).
Add the library to your Cargo.toml:
[dependencies]
cull-gmail = "0.0.16"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
Here's a minimal example to get started:
use cull_gmail::{ClientConfig, GmailClient, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Load configuration from file or environment
let config = ClientConfig::builder()
.with_credential_file("credential.json")
.build();
// Create Gmail client and authenticate
let mut client = GmailClient::new_with_config(config).await?;
// List first 10 messages
client.set_max_results(10);
client.get_messages(1).await?;
client.log_messages().await?;
Ok(())
}
The main client for interacting with Gmail API:
use cull_gmail::{GmailClient, MessageList};
// Create client with configuration
let mut client = GmailClient::new_with_config(config).await?;
// Query messages with Gmail search syntax
client.set_query("older_than:1y label:promotions");
client.add_labels(&["INBOX".to_string()])?;
client.set_max_results(200);
// Get messages (0 = all pages, 1 = first page only)
client.get_messages(0).await?;
// Access message data
let messages = client.messages();
let message_ids = client.message_ids();
Handles authentication and configuration:
use cull_gmail::ClientConfig;
// From credential file
let config = ClientConfig::builder()
.with_credential_file("path/to/credential.json")
.with_config_path(".cull-gmail")
.build();
// From individual OAuth2 parameters
let config = ClientConfig::builder()
.with_client_id("your-client-id")
.with_client_secret("your-client-secret")
.with_auth_uri("https://accounts.google.com/o/oauth2/auth")
.with_token_uri("https://oauth2.googleapis.com/token")
.add_redirect_uri("http://localhost:8080")
.build();
Define automated message lifecycle rules:
use cull_gmail::{Rules, Retention, MessageAge, EolAction};
// Create a rule set
let mut rules = Rules::new();
// Add retention rules
rules.add_rule(
Retention::new(MessageAge::Years(1), true),
Some(&"old-emails".to_string()),
false // false = trash, true = delete
);
rules.add_rule(
Retention::new(MessageAge::Months(6), true),
Some(&"promotions".to_string()),
false
);
// Save rules to file
rules.save()?;
// Load existing rules
let loaded_rules = Rules::load()?;
Batch operations on messages:
use cull_gmail::{RuleProcessor, EolAction};
// Set up rule and dry-run mode
client.set_execute(false); // Dry run - no actual changes
let rule = rules.get_rule(1).unwrap();
client.set_rule(rule);
// Find messages matching rule for a label
client.find_rule_and_messages_for_label("promotions").await?;
// Check what action would be performed
if let Some(action) = client.action() {
match action {
EolAction::Trash => println!("Would move {} messages to trash", client.messages().len()),
EolAction::Delete => println!("Would delete {} messages permanently", client.messages().len()),
}
}
// Execute for real
client.set_execute(true);
match client.action() {
Some(EolAction::Trash) => client.batch_trash().await?,
Some(EolAction::Delete) => client.batch_delete().await?,
None => println!("No action specified"),
}
let config = ClientConfig::builder()
.with_credential_file("path/to/credential.json")
.build();
The library supports TOML configuration files (default: ~/.cull-gmail/cull-gmail.toml):
credentials = "credential.json"
config_root = "~/.cull-gmail"
rules = "rules.toml"
execute = false
# Alternative: direct OAuth2 parameters
# client_id = "your-client-id"
# client_secret = "your-client-secret"
# token_uri = "https://oauth2.googleapis.com/token"
# auth_uri = "https://accounts.google.com/o/oauth2/auth"
Override configuration with environment variables:
export APP_CREDENTIALS="/path/to/credential.json"
export APP_EXECUTE="true"
export APP_CLIENT_ID="your-client-id"
export APP_CLIENT_SECRET="your-client-secret"
The library uses a comprehensive error type:
use cull_gmail::{Error, Result};
match client.get_messages(1).await {
Ok(_) => println!("Success!"),
Err(Error::NoLabelsFound) => println!("No labels found in mailbox"),
Err(Error::LabelNotFoundInMailbox(label)) => println!("Label '{}' not found", label),
Err(Error::GoogleGmail1(e)) => println!("Gmail API error: {}", e),
Err(e) => println!("Other error: {}", e),
}
Common error types:
NoLabelsFound: Mailbox has no labelsLabelNotFoundInMailbox(String): Specific label not foundRuleNotFound(usize): Rule ID doesn't existGoogleGmail1(Box<google_gmail1::Error>): Gmail API errorsStdIO(std::io::Error): File I/O errorsConfig(config::ConfigError): Configuration errorsThe library requires an async runtime (Tokio recommended):
[dependencies]
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
#[tokio::main]
async fn main() -> cull_gmail::Result<()> {
// Your code here
Ok(())
}
The library supports Gmail's search syntax for message queries:
// Date-based queries
client.set_query("older_than:1y"); // Older than 1 year
client.set_query("newer_than:30d"); // Newer than 30 days
client.set_query("after:2023/1/1"); // After specific date
// Label-based queries
client.set_query("label:promotions"); // Has promotions label
client.set_query("-label:important"); // Does NOT have important label
// Content queries
client.set_query("subject:newsletter"); // Subject contains "newsletter"
client.set_query("from:noreply@example.com"); // From specific sender
// Combined queries
client.set_query("label:promotions older_than:6m -is:starred");
client.set_max_results(n) to adjustclient.get_messages(0) to get all pagesclient.get_messages(n) to limit to n pagesgoogle-gmail1 crateThe library uses the log crate for logging:
use env_logger;
// Initialize logging
env_logger::init();
# Set log level via environment variable
# RUST_LOG=cull_gmail=debug cargo run
Log levels:
error: Critical errorswarn: Warnings (e.g., missing labels, dry-run mode)info: General information (e.g., message subjects, action results)debug: Detailed operation infotrace: Very detailed debugging info~/.cull-gmail/gmail1 by defaultThe library requires the https://mail.google.com/ scope for full Gmail access.
rm -rf ~/.cull-gmail/gmail1client.show_label()client.set_max_results(100)By contributing to cull-gmail, you agree that your contributions will be licensed under the MIT License. This means:
Thank you for your interest in contributing to cull-gmail! We welcome contributions from the community and appreciate your help in making this project better.
Further details can be found in the contribution document.