use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use anyhow::{Context, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SmtpConfig {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
pub tls: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImapConfig {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
pub tls: bool,
pub inbox_folder: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keybinds {
pub quit: String,
pub compose: String,
pub reply: String,
pub delete: String,
pub star: String,
pub sync: String,
pub next_folder: String,
pub prev_folder: String,
}
impl Default for Keybinds {
fn default() -> Self {
Self {
quit: "q".into(),
compose: "c".into(),
reply: "r".into(),
delete: "d".into(),
star: "s".into(),
sync: "S".into(),
next_folder: "Tab".into(),
prev_folder: "BackTab".into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub identity: String,
pub display_name: Option<String>,
#[serde(default = "default_domain")]
pub domain: String,
pub smtp: SmtpConfig,
pub imap: ImapConfig,
#[serde(default)]
pub keybinds: Keybinds,
}
fn default_domain() -> String {
"gmail.com".to_string()
}
impl Config {
pub fn resolve_address(&self, input: &str) -> String {
if input.contains('@') {
input.to_string()
} else {
format!("{}@{}", input, self.domain)
}
}
pub fn config_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("mail-rs")
.join("config.toml")
}
pub fn load() -> Result<Self> {
let path = Self::config_path();
if !path.exists() {
let example = Self::example_toml();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, &example)?;
eprintln!("No config found. A starter config has been written to:");
eprintln!(" {}", path.display());
eprintln!();
eprintln!("Edit it with your SMTP/IMAP credentials, then re-run mail.");
std::process::exit(0);
}
let raw = fs::read_to_string(&path)
.with_context(|| format!("Cannot read {}", path.display()))?;
toml::from_str(&raw)
.with_context(|| format!("Invalid TOML in {}", path.display()))
}
fn example_toml() -> String {
r#"# mail-rs configuration
# ~/.config/mail-rs/config.toml
identity = "you@gmail.com"
display_name = "Your Name" # optional
domain = "gmail.com" # bare usernames expand to this: mail send hanzo → hanzo@gmail.com
[smtp]
host = "smtp.gmail.com"
port = 587
username = "you@gmail.com"
password = "your-app-password" # use a Gmail App Password, not your real password
tls = true
[imap]
host = "imap.gmail.com"
port = 993
username = "you@gmail.com"
password = "your-app-password"
tls = true
inbox_folder = "INBOX"
[keybinds]
quit = "q"
compose = "c"
reply = "r"
delete = "d"
star = "s"
sync = "S"
next_folder = "Tab"
prev_folder = "BackTab"
"#
.to_string()
}
}