Thin CLI dispatch over mail-sync: composite-account (paired MailReader/MailSender) config, sync/send/accounts commands. Sprint 3.45 crate 11 — mm shrinks to dispatch-only, provider selection lives in config, TUI stays out of v1.
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/mail-manager |
| Edition | 2021 |
| Targets | tools-mail-manager, tools_mail_manager |
| Public items | 35 across 7 modules |
| Tests | 38 |
What it is for
tools-mail-manager — thin CLI dispatch over operations-mail-sync (Sprint 3.45 crate 11, last of the sprint).
config: the account-config TOML schema. One entry per logical
account pairs an optional reader and an optional sender (AC-composite-account) — never a 1:1 provider-per-account model.
factory: builds aBox<dyn MailReader>/Box<dyn MailSender>from
an account's config, resolving credentials at build time.
store: a file-backedoperations_mail_sync::MailStore— the persistence seam
the sync worker writes cursors/messages through. Not Postgres: a v1 CLI tool's own local state, not a shared service (see CHANGELOG.md).
audit: a file-backedoperations_mail_sync::AuditSink— append-only JSONL of
every send/throttle/deny event SendGuard produces.
cli: the clap surface (sync/send/accounts list).error: this crate's own error type, wrappingoperations-mail-sync's.
Capabilities
FileAuditSink
FileAuditSink — a file-backed operations_mail_sync::AuditSink.
| Item |
|---|
pub struct FileAuditSink |
FileAuditSink :: fn new(path : impl Into <PathBuf>) -> Self |
FileAuditSink :: async fn record(& self, log : AuditLog) -> Result <(), String> |
AccountsCmd
CLI surface: tools-mail-manager sync <account>, `tools-mail-manager send <account>
| Item |
|---|
pub enum AccountsCmd |
Cli
CLI surface: tools-mail-manager sync <account>, `tools-mail-manager send <account>
| Item |
|---|
pub struct Cli |
Command
CLI surface: tools-mail-manager sync <account>, `tools-mail-manager send <account>
| Item |
|---|
pub enum Command |
AccountConfig
Account config: TOML shape, one entry per logical account, each entry
| Item |
|---|
pub struct AccountConfig |
AccountConfig :: fn reader_kind(& self) -> Option <& 'static str> |
AccountConfig :: fn sender_kind(& self) -> Option <& 'static str> |
CredentialRef
Account config: TOML shape, one entry per logical account, each entry
| Item |
|---|
pub enum CredentialRef |
CredentialRef :: fn resolve(& self) -> Result <(String, String), MailManagerError> |
MailManagerConfig
Account config: TOML shape, one entry per logical account, each entry
| Item |
|---|
pub struct MailManagerConfig |
fn load_config(path : & Path) -> Result <MailManagerConfig, MailManagerError> |
fn parse_config(text : & str) -> Result <MailManagerConfig, MailManagerError> |
ReaderConfig
Account config: TOML shape, one entry per logical account, each entry
| Item |
|---|
pub enum ReaderConfig |
SenderConfig
Account config: TOML shape, one entry per logical account, each entry
| Item |
|---|
pub enum SenderConfig |
MailManagerError
Errors for tools-mail-manager's config/factory/store/audit wiring.
| Item |
|---|
pub enum MailManagerError |
factory (other)
Builds a Box<dyn MailReader> / Box<dyn MailSender> from one account's
| Item |
|---|
fn build_reader(account : & str, cfg : & ReaderConfig,) -> Result <Box <dyn MailReader>, MailManagerError> |
fn build_sender(account : & str, cfg : & SenderConfig,) -> Result <Box <dyn MailSender>, MailManagerError> |
run (other)
Dispatch glue: turns one parsed crate::cli::Command plus a loaded
| Item |
|---|
pub const CONFIG_ENV_VAR: & str |
pub const DEFAULT_CONFIG_FILE: & str |
fn resolve_config_path(explicit : Option <& Path>) -> PathBuf |
fn default_state_dir() -> PathBuf |
fn state_file_path(state_dir : & Path) -> PathBuf |
fn audit_file_path(audit_dir : & Path) -> PathBuf |
fn find_account <'a>(config : & 'a MailManagerConfig, account_name : & str,) -> Result <& 'a AccountConfig, MailManagerError> |
async fn run_sync(account_name : & str, account : & AccountConfig, store : & dyn MailStore, mailbox : Option <& str>, full : bool, override_filter : Option <& str>, page_size : u32, max_messages : u32,) -> Result <SyncOutcome, MailManagerError> |
async fn run_send(account_name : & str, account : & AccountConfig, audit : & dyn AuditSink, quota_per_day : u64, to : Vec <String>, cc : Vec <String>, bcc : Vec <String>, subject : String, body : String, override_quota : bool,) -> Result <SendReceipt, MailManagerError> |
AccountSummary
Dispatch glue: turns one parsed crate::cli::Command plus a loaded
| Item |
|---|
pub struct AccountSummary |
fn accounts_summary(config : & MailManagerConfig) -> Vec <AccountSummary> |
FileMailStore
FileMailStore — a file-backed operations_mail_sync::MailStore.
| Item |
|---|
pub struct FileMailStore |
FileMailStore :: fn open(path : impl Into <PathBuf>) -> Result <Self, MailManagerError> |
FileMailStore :: async fn get_cursor(& self, account_id : & str, mailbox : Option <& str>,) -> Result <Option <Cursor>, String> |
FileMailStore :: async fn save_cursor(& self, account_id : & str, mailbox : Option <& str>, cursor : Cursor,) -> Result <(), String> |
FileMailStore :: async fn save_message(& self, account_id : & str, email : & Email) -> Result <bool, String> |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
tools_mail_manager
auditcliconfigerrorfactoryrunstore
flowchart TD n_tools_mail_manager["tools_mail_manager"] n_tools_mail_manager --> n_audit["audit"] n_tools_mail_manager --> n_cli["cli"] n_tools_mail_manager --> n_config["config"] n_tools_mail_manager --> n_error["error"] n_tools_mail_manager --> n_factory["factory"] n_tools_mail_manager --> n_run["run"] n_tools_mail_manager --> n_store["store"]
Public surface
`audit`
| Item | What it is |
|---|---|
pub struct FileAuditSink | Appends every SendGuard audit event to a JSONL file. |
FileAuditSink :: fn new(path : impl Into <PathBuf>) -> Self | Point the sink at path |
FileAuditSink :: async fn record(& self, log : AuditLog) -> Result <(), String> | — |
`cli`
| Item | What it is |
|---|---|
pub struct Cli | tools-mail-manager — thin CLI dispatch over operations-mail-sync |
pub enum Command | Top-level verbs. |
pub enum AccountsCmd | accounts ... subcommands. |
`config`
| Item | What it is |
|---|---|
pub struct MailManagerConfig | The whole config file: one entry per logical account, keyed by the operator-chosen account name (southcity, personal-gmail, ...). |
pub struct AccountConfig | One logical account: an optional read capability and an optional send capability, independently configured (AC-composite-account) |
AccountConfig :: fn reader_kind(& self) -> Option <& 'static str> | A short tag naming which reader backend is configured, for accounts list output — never None/Some Debug noise. |
AccountConfig :: fn sender_kind(& self) -> Option <& 'static str> | Same as Self::reader_kind for the sender side. |
pub enum ReaderConfig | A read-capability backend |
pub enum SenderConfig | A send-capability backend |
pub enum CredentialRef | Where a provider's AUTH username/password come from |
CredentialRef :: fn resolve(& self) -> Result <(String, String), MailManagerError> | Resolve to a (username, password) pair |
fn load_config(path : & Path) -> Result <MailManagerConfig, MailManagerError> | Parse a config file at path into a MailManagerConfig |
fn parse_config(text : & str) -> Result <MailManagerConfig, MailManagerError> | Parse a config file's contents (test seam — avoids touching disk in unit tests that only care about shape validation) |
`error`
| Item | What it is |
|---|---|
pub enum MailManagerError | Errors raised while resolving config, building a provider pair, or driving operations-mail-sync for one account. |
`factory`
| Item | What it is |
|---|---|
fn build_reader(account : & str, cfg : & ReaderConfig,) -> Result <Box <dyn MailReader>, MailManagerError> | Build the MailReader an account's reader config describes |
fn build_sender(account : & str, cfg : & SenderConfig,) -> Result <Box <dyn MailSender>, MailManagerError> | Build the MailSender an account's sender config describes |
`run`
| Item | What it is |
|---|---|
pub const CONFIG_ENV_VAR: & str | Env var naming an explicit config path, checked when --config is not passed on the command line. |
pub const DEFAULT_CONFIG_FILE: & str | Default config file name, relative to the current directory, used when neither --config nor $MAIL_MANAGER_CONFIG is set. |
fn resolve_config_path(explicit : Option <& Path>) -> PathBuf | Resolve the config path: --config wins, then $MAIL_MANAGER_CONFIG, then ./tools-mail-manager.toml. |
fn default_state_dir() -> PathBuf | Default local-state directory: the XDG data dir's tools-mail-manager/ (falls back to .tools-mail-manager in the current directory if no data dir can be determined, e.g |
fn state_file_path(state_dir : & Path) -> PathBuf | The sync-state file within a state directory. |
fn audit_file_path(audit_dir : & Path) -> PathBuf | The audit-log file within an audit directory. |
pub struct AccountSummary | One row of accounts list output: the account name and each capability's provider kind (None = not configured). |
fn accounts_summary(config : & MailManagerConfig) -> Vec <AccountSummary> | Summarize every configured account, sorted by name (the config's BTreeMap already iterates in that order). |
fn find_account <'a>(config : & 'a MailManagerConfig, account_name : & str,) -> Result <& 'a AccountConfig, MailManagerError> | Look up account_name in config, erroring loudly if it isn't there |
async fn run_sync(account_name : & str, account : & AccountConfig, store : & dyn MailStore, mailbox : Option <& str>, full : bool, override_filter : Option <& str>, page_size : u32, max_messages : u32,) -> Result <SyncOutcome, MailManagerError> | Run sync for one account: build its reader, then drive operations_mail_sync::SyncWorker against store |
async fn run_send(account_name : & str, account : & AccountConfig, audit : & dyn AuditSink, quota_per_day : u64, to : Vec <String>, cc : Vec <String>, bcc : Vec <String>, subject : String, body : String, override_quota : bool,) -> Result <SendReceipt, MailManagerError> | Run send for one account: build its sender, then drive operations_mail_sync::SendGuard (per-recipient daily quota + mandatory audit) |
`store`
| Item | What it is |
|---|---|
pub struct FileMailStore | A file-backed MailStore |
FileMailStore :: fn open(path : impl Into <PathBuf>) -> Result <Self, MailManagerError> | Open (or create) the store backed by the JSON file at path |
FileMailStore :: async fn get_cursor(& self, account_id : & str, mailbox : Option <& str>,) -> Result <Option <Cursor>, String> | — |
FileMailStore :: async fn save_cursor(& self, account_id : & str, mailbox : Option <& str>, cursor : Cursor,) -> Result <(), String> | — |
FileMailStore :: async fn save_message(& self, account_id : & str, email : & Email) -> Result <bool, String> | — |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
FileAuditSink | audit::FileAuditSink |
FileMailStore | store::FileMailStore |
MailManagerError | error::MailManagerError |
{AccountsCmd,Cli,Command} | cli::{AccountsCmd,Cli,Command} |
{accounts_summary,audit_file_path,default_state_dir,find_account,resolve_config_path,run_send,run_sync,state_file_path,AccountSummary,CONFIG_ENV_VAR,DEFAULT_CONFIG_FILE,} | run::{accounts_summary,audit_file_path,default_state_dir,find_account,resolve_config_path,run_send,run_sync,state_file_path,AccountSummary,CONFIG_ENV_VAR,DEFAULT_CONFIG_FILE,} |
{build_reader,build_sender} | factory::{build_reader,build_sender} |
{load_config,AccountConfig,CredentialRef,MailManagerConfig,ReaderConfig,SenderConfig,} | config::{load_config,AccountConfig,CredentialRef,MailManagerConfig,ReaderConfig,SenderConfig,} |
Boundary
Reaches into foundation, infrastructure, operations.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/mail-manager |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_tools["tools"] --> n_foundation["foundation"] n_tools["tools"] --> n_infrastructure["infrastructure"] n_tools["tools"] --> n_operations["operations"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-audit-log` | foundation | no | always |
| `foundation-mail-message` | foundation | no | always |
| `foundation-refresh-policy` | foundation | no | always |
| `infrastructure-adapters-google-gmail` | infrastructure | no | always |
| `infrastructure-imap` | infrastructure | no | always |
| `infrastructure-smtp` | infrastructure | no | always |
| `operations-mail-sync` | operations | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
clap | ^4 | derive | no | always |
dirs | ^5 | — | no | always |
serde | ^1 | derive, derive | no | always |
serde_json | ^1 | — | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full, macros, rt-multi-thread | no | always |
toml | ^0.8 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `tools-cli-conformance` | tools | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tempfile | ^3 | — | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["tools-mail-manager"] SELF -->|development| n_tools_cli_conformance["tools-cli-conformance"] SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"] SELF -->|runtime| n_foundation_mail_message["foundation-mail-message"] SELF -->|runtime| n_foundation_refresh_policy["foundation-refresh-policy"] SELF -->|runtime| n_infrastructure_adapters_google_gmail["infrastructure-adapters-google-gmail"] SELF -->|runtime| n_infrastructure_imap["infrastructure-imap"] SELF -->|runtime| n_infrastructure_smtp["infrastructure-smtp"] SELF -->|runtime| n_operations_mail_sync["operations-mail-sync"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | tools-mail-manager | `src/main.rs` |
| lib | tools_mail_manager | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
MailManagerError | build_reader, build_sender, find_account, load_config, parse_config, run_send, … (7 total) |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 38 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
audit | 1 | 0 | 0 |
cli | 3 | 0 | 0 |
config | 7 | 0 | 0 |
error | 1 | 0 | 0 |
factory | 2 | 0 | 0 |
run | 11 | 0 | 0 |
store | 1 | 0 | 0 |
What the tests establish, by name:
creates_parent_directory_and_file_on_first_write—src/audit.rsrecord_appends_one_json_line_per_call—src/audit.rsaccounts_list_parses—src/cli.rsbare_invocation_is_an_error_that_shows_usage—src/cli.rscli_definition_conforms—src/cli.rsglobal_flags_parse_before_the_subcommand—src/cli.rssend_parses_repeatable_to_and_flags—src/cli.rssend_requires_at_least_one_to_address—src/cli.rssync_parses_account_and_defaults—src/cli.rssync_parses_all_flags—src/cli.rsac_composite_account_pairs_imap_reader_with_smtp_sender_under_one_account—src/config.rscredential_ref_reports_missing_var_by_name—src/config.rscredential_ref_resolves_from_env—src/config.rsgmail_account_has_both_capabilities_from_the_same_oauth_files—src/config.rsmalformed_toml_is_a_config_error—src/config.rsmissing_required_field_is_a_config_error—src/config.rssend_only_account_has_no_reader—src/config.rsunknown_account_name_is_absent_not_a_parse_error—src/config.rsbuild_reader_gmail_fails_provider_error_on_missing_files—src/factory.rsbuild_reader_imap_fails_loudly_on_missing_credential_env_var—src/factory.rsbuild_reader_imap_succeeds_with_resolved_env_credentials—src/factory.rsbuild_sender_smtp_bad_from_address_is_a_provider_error—src/factory.rsbuild_sender_smtp_missing_credential_env_var_fails_loudly—src/factory.rsbuild_sender_smtp_no_auth_relay_succeeds_without_credentials—src/factory.rsbuild_sender_smtp_with_credentials_resolves_env_first—src/factory.rsaccounts_summary_reports_each_accounts_capabilities—src/run.rsfind_account_errors_loudly_on_unknown_name—src/run.rsresolve_config_path_falls_back_to_default_file—src/run.rsresolve_config_path_falls_back_to_env_var—src/run.rsresolve_config_path_prefers_explicit_flag—src/run.rs- _… 8 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 31 | 35 |
Public modules with a //! block | 7 | 7 |
pie showData
title Public items with rustdoc
"Documented" : 31
"No rustdoc detected" : 4
Metrics
| Metric | Value |
|---|---|
| Rust source files | 9 |
| Source lines | 1945 |
| Code lines | 1465 |
| Public API items | 35 |
| Public modules | 7 |
| Tests | 38 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 16 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 2
"enum" : 6
"function" : 12
"method" : 9
"struct" : 6
pie showData
title Rust source composition
"Code" : 1465
"Blank or comment" : 480
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.