Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 91 additions & 92 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[package]
name = "ion-mail"
version = "0.2.0"
version = "0.2.3"
edition = "2024"
description = "A simple mail CLI"
authors = ["Paul8711gamezz <support@paul8711gamezz.org>"]
description = "Mail CLI in Rust supporting all mail functions & OAuth2 login"
authors = ["Paul8711 <support@paul8711.com>"]
license = "AGPL-3.0-only"
repository = "https://github.com/Paulprojects8711/ion-mail"
repository = "https://github.com/paul8711-code/ion-mail"

[package.metadata.wix]
upgrade-guid = "E939105E-8B38-4C78-80D6-65E9FB114071"
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -629,8 +629,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

ion-mail Heavyweight CLI email client (in Rust)
Copyright (C) 2026 Paul8711gamezz (Paulprojects8711)
ion-mail Mail CLI in Rust supporting all mail functions & OAuth2 login
Copyright (C) 2026 Paul8711

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ion-mail
Heavyweight CLI email client (in Rust)
Mail CLI in Rust supporting all mail functions & OAuth2 login

Please report any bugs you find, some cannot be fixed because they are server related.

Expand Down
1,287 changes: 541 additions & 746 deletions src/account.rs

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(author, version, about = "A simple mail CLI")]
#[command(
author,
version,
about = "Mail CLI in Rust supporting all mail functions & OAuth2 login"
)]
pub struct Cli {
#[command(subcommand)]
pub resource: Resource,
Expand Down
215 changes: 85 additions & 130 deletions src/folder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,11 @@ use std::process;
pub fn list(config: Config, stats: bool) {
let mut imap_session = auth(config);

let mailboxes = match imap_session.list(None, Some("*")) {
Ok(m) => m,
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
};
let mailboxes = imap_session.list(None, Some("*")).unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

let mut msgs: Vec<(u32, String)> = Vec::new();

Expand All @@ -24,18 +21,16 @@ pub fn list(config: Config, stats: bool) {
.attributes()
.contains(&imap::types::NameAttribute::NoSelect)
{
let msg_cnt = match imap_session.examine(mailbox.name()) {
Ok(cnt) => cnt,
Err(e) => {
eprintln!(
"Failed to retrieve total messages for folder {}: {}",
mailbox.name(),
e
);
keyring_core::unset_default_store();
process::exit(1);
}
};
let msg_cnt = imap_session.examine(mailbox.name()).unwrap_or_else(|err| {
eprintln!(
"Failed to retrieve total messages for folder {}: {}",
mailbox.name(),
err
);
keyring_core::unset_default_store();
process::exit(1);
});

msgs.push((msg_cnt.exists, mailbox.name().to_string()));
}
} else {
Expand All @@ -51,49 +46,40 @@ pub fn list(config: Config, stats: bool) {
}
}

match imap_session.logout() {
Ok(_) => {}
Err(e) => {
eprintln!("Logout failed, ignoring... ({})", e);
}
};
imap_session.logout().unwrap_or_else(|err| {
eprintln!("Logout failed, ignoring... ({})", err);
});
}

pub fn view(config: Config, folder: String, page_size: usize) {
let mut imap_session = auth(config.clone());

match imap_session.select(&folder) {
Ok(_) => {}
Err(e) => {
eprintln!("Failed to select folder: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
}
imap_session.select(&folder).unwrap_or_else(|err| {
eprintln!("Failed to select folder: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

let mails = match imap_session.fetch("1:*", "(BODY.PEEK[HEADER])") {
Ok(m) => m,
Err(e) => {
eprintln!("Failed to fetch messages: {}", e);
let mails = imap_session
.fetch("1:*", "(BODY.PEEK[HEADER])")
.unwrap_or_else(|err| {
eprintln!("Failed to fetch messages: {}", err);
keyring_core::unset_default_store();
process::exit(1);
}
};
});

let mut current_id = 0;

let mut messages: Vec<String> = Vec::new();

for mail in mails.iter().rev() {
if let Some(header_bytes) = mail.header() {
let (parsed_headers, _) = match mailparse::parse_headers(header_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to parse headers: {}", e);
let (parsed_headers, _) =
mailparse::parse_headers(header_bytes).unwrap_or_else(|err| {
eprintln!("Failed to parse headers: {}", err);
keyring_core::unset_default_store();
process::exit(1);
}
};
});

let mut subject = String::new();
let mut from = String::new();
Expand All @@ -111,14 +97,13 @@ pub fn view(config: Config, folder: String, page_size: usize) {
}
}

let parsed_date = match DateTime::parse_from_rfc2822(&date) {
Ok(p) => p.with_timezone(&Local),
Err(e) => {
eprintln!("Failed to parse date: {}", e);
let parsed_date = DateTime::parse_from_rfc2822(&date)
.unwrap_or_else(|err| {
eprintln!("Failed to parse date: {}", err);
keyring_core::unset_default_store();
process::exit(1);
}
};
})
.with_timezone(&Local);

messages.push(format!(
"[{:03}] {} | {} | {}",
Expand All @@ -132,12 +117,9 @@ pub fn view(config: Config, folder: String, page_size: usize) {
}
}

match imap_session.logout() {
Ok(_) => {}
Err(e) => {
eprintln!("Logout failed, ignoring... ({})", e);
}
};
imap_session.logout().unwrap_or_else(|err| {
eprintln!("Logout failed, ignoring... ({})", err);
});

if !messages.is_empty() {
let selection = Select::with_theme(&ColorfulTheme::default())
Expand All @@ -149,14 +131,12 @@ pub fn view(config: Config, folder: String, page_size: usize) {
.unwrap();

if let Some(selection) = selection {
let s = match selection.try_into() {
Ok(s) => s,
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
};
let s = selection.try_into().unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

read(config, folder, s);
}
} else {
Expand All @@ -167,14 +147,11 @@ pub fn view(config: Config, folder: String, page_size: usize) {
pub fn create(config: Config, name: String, parents: bool) {
let mut imap_session = auth(config);

let mailboxes = match imap_session.list(None, Some("*")) {
Ok(m) => m,
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
};
let mailboxes = imap_session.list(None, Some("*")).unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

let delimiter = mailboxes[0].delimiter().unwrap();

Expand Down Expand Up @@ -202,12 +179,9 @@ pub fn create(config: Config, name: String, parents: bool) {
}
}

match imap_session.logout() {
Ok(_) => {}
Err(e) => {
eprintln!("Logout failed, ignoring... ({})", e);
}
}
imap_session.logout().unwrap_or_else(|err| {
eprintln!("Logout failed, ignoring... ({})", err);
});
}

pub fn delete(config: Config, names: Vec<String>, recursive: bool, yes: bool) {
Expand All @@ -228,14 +202,11 @@ pub fn delete(config: Config, names: Vec<String>, recursive: bool, yes: bool) {

if confirmation {
if recursive {
let mailboxes = match imap_session.list(None, Some("*")) {
Ok(m) => m,
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
};
let mailboxes = imap_session.list(None, Some("*")).unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

let mut del_mailboxes = Vec::new();

Expand Down Expand Up @@ -273,12 +244,9 @@ pub fn delete(config: Config, names: Vec<String>, recursive: bool, yes: bool) {
}
}

match imap_session.logout() {
Ok(_) => {}
Err(e) => {
eprintln!("Logout failed, ignoring... ({})", e);
}
}
imap_session.logout().unwrap_or_else(|err| {
eprintln!("Logout failed, ignoring... ({})", err);
});
}

pub fn empty(config: Config, name: String) {
Expand All @@ -292,58 +260,45 @@ pub fn empty(config: Config, name: String) {
{
let mut imap_session = auth(config);

match imap_session.select(&name) {
Ok(_) => {}
Err(e) => {
eprintln!("Failed to select folder: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
}
imap_session.select(&name).unwrap_or_else(|err| {
eprintln!("Failed to select folder: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

let search_results = match imap_session.uid_search("ALL") {
Ok(s) => s,
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
};
let search_results = imap_session.uid_search("ALL").unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

if !search_results.is_empty() {
let uid_list: Vec<String> = search_results.iter().map(|id| id.to_string()).collect();
let uid_set = uid_list.join(",");

match imap_session.uid_store(&uid_set, "FLAGS (\\Deleted)") {
Ok(_) => {}
Err(e) => {
eprintln!("Unexpected Error: {}", e);
imap_session
.uid_store(&uid_set, "FLAGS (\\Deleted)")
.unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
}
}
});

// store doesnt work on some servers

match imap_session.expunge() {
Ok(_) => {}
Err(e) => {
eprintln!("Unexpected Error: {}", e);
keyring_core::unset_default_store();
process::exit(1);
}
}
imap_session.expunge().unwrap_or_else(|err| {
eprintln!("Unexpected Error: {}", err);
keyring_core::unset_default_store();
process::exit(1);
});

println!("Folder {} emptied", name);
} else {
println!("Folder {} is already empty", name);
}

match imap_session.logout() {
Ok(_) => {}
Err(e) => {
eprintln!("Logout failed, ignoring... ({})", e);
}
}
imap_session.logout().unwrap_or_else(|err| {
eprintln!("Logout failed, ignoring... ({})", err);
});
}
}
Loading
Loading