Aliasman is a tool for managing a large number of email aliases, with both a CLI and a web frontend. It supports pluggable storage and email providers, allowing you to manage alias metadata locally while controlling the actual email routing through your email service provider.
Aliasman is structured as a Cargo workspace with separate crates for the core library and frontends:
aliasman/
├── Cargo.toml # Workspace root
├── crates/
│ ├── aliasman-core/ # Library: models, traits, providers, business logic
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── model.rs # Alias, AliasFilter
│ │ ├── error.rs # Typed error types (thiserror)
│ │ ├── config.rs # AppConfig with serde + config crate
│ │ ├── storage/
│ │ │ ├── mod.rs # StorageProvider trait
│ │ │ ├── sqlite.rs # SQLite implementation (sqlx)
│ │ │ └── s3.rs # S3 implementation (aws-sdk-s3)
│ │ └── email/
│ │ ├── mod.rs # EmailProvider trait
│ │ └── rackspace.rs # Rackspace Email implementation
│ ├── aliasman-cli/ # Binary: CLI frontend
│ │ └── src/
│ │ ├── main.rs
│ │ ├── commands/
│ │ │ ├── mod.rs
│ │ │ ├── alias.rs # alias create/edit/delete/list/suspend/search
│ │ │ ├── config.rs # config command
│ │ │ └── storage.rs # storage convert command
│ │ └── output.rs # Table formatting (comfy-table)
│ └── aliasman-web/ # Binary: web frontend
│ ├── src/
│ │ ├── main.rs # Axum server, CLI args, startup
│ │ ├── routes.rs # HTTP handlers and Askama templates
│ │ ├── state.rs # Shared application state
│ │ ├── error.rs # Web error type
│ │ └── auth.rs # Auth/RBAC stubs (future)
│ ├── templates/ # Askama HTML templates
│ └── static/ # Embedded static assets (htmx.min.js)
- Fully async — tokio runtime throughout, including storage. The rackspace-email crate is async and the future web frontend benefits from this.
- Workspace with lib + bins — Core logic lives in
aliasman-coreso both the CLI and web frontend consume it. - Enum dispatch for providers — Provider selection uses Rust enums with
serde(tag = "type")rather than dynamic registration. Type-safe and exhaustive at compile time. - Dual-write pattern — Mutations (create, edit, delete, suspend, unsuspend) write to both the email provider and storage provider. The email provider manages actual email routing; storage maintains metadata, timestamps, and descriptions.
- Testable provider wrappers — External API clients (e.g.
RackspaceClient) are wrapped behind internal traits (RackspaceClientImpl) so they can be replaced with mocks in tests without hitting real services. - thiserror + anyhow —
thiserrorfor typed errors in the library,anyhowfor ergonomic error propagation in the CLI binary. - TOML configuration — Config file at
~/.config/aliasman/config.toml, loaded via theconfigcrate with environment variable overrides. - Multi-system support — Configuration supports multiple named "systems" (e.g. "home", "work"), each pairing a storage and email provider with per-system defaults. Select via
--systemflag ordefault_systemin config.
Storage providers manage alias metadata (descriptions, timestamps, suspension state):
| Provider | Status | Description |
|---|---|---|
sqlite |
Implemented | SQLite via sqlx. Default: ~/.config/aliasman/aliasman.db |
postgres |
Implemented | PostgreSQL via sqlx. Suitable for shared/container deployments |
s3 |
Implemented | AWS S3 with per-alias objects and an index blob |
files |
Planned | JSON files on the local filesystem |
Email providers manage actual email routing:
| Provider | Status | Description |
|---|---|---|
rackspace |
Implemented | Rackspace Email API via the rackspace-email crate |
gsuite |
Planned | Google Workspace Admin API |
#[async_trait]
pub trait StorageProvider: Send + Sync {
async fn open(&mut self, read_only: bool) -> Result<()>;
async fn close(&mut self) -> Result<()>;
async fn get(&self, alias: &str, domain: &str) -> Result<Option<Alias>>;
async fn put(&self, alias: &Alias) -> Result<()>;
async fn update(&self, alias: &Alias) -> Result<()>;
async fn delete(&self, alias: &str, domain: &str) -> Result<()>;
async fn search(&self, filter: &AliasFilter) -> Result<Vec<Alias>>;
async fn suspend(&self, alias: &str, domain: &str) -> Result<()>;
async fn unsuspend(&self, alias: &str, domain: &str) -> Result<()>;
}
#[async_trait]
pub trait EmailProvider: Send + Sync {
async fn alias_create(&self, alias: &str, domain: &str, addresses: &[String]) -> Result<()>;
async fn alias_delete(&self, alias: &str, domain: &str) -> Result<()>;
async fn alias_list(&self, domain: &str) -> Result<Vec<Alias>>;
}| Crate | Purpose |
|---|---|
tokio |
Async runtime |
clap (derive) |
CLI argument parsing |
serde + toml |
Config serialization |
config |
Layered config loading (files, env vars) |
sqlx (sqlite) |
Async SQLite storage |
rackspace-email |
Rackspace Email API client |
chrono |
Timestamps |
regex |
Alias filtering |
comfy-table |
CLI table output |
thiserror |
Library error types |
anyhow |
CLI error handling |
async-trait |
Async trait support |
aws-sdk-s3 |
AWS S3 SDK |
aws-config |
AWS configuration and credentials |
serde_json |
JSON serialization for S3 storage |
rand |
Random alias generation |
axum |
Web server framework |
askama |
Compile-time HTML templates |
rust-embed |
Embed static assets in binary |
htmx |
Dynamic interactions (JS, embedded) |
Download the latest CLI binary from GitHub Releases:
aliasman-x86_64-linux.tar.gz— x86_64 Linuxaliasman-aarch64-macos.tar.gz— Apple Silicon macOS
tar -xzf aliasman-*.tar.gz
sudo mv aliasman /usr/local/bin/The Docker image includes both the CLI and web server. The web server starts by default:
docker run -p 3000:3000 \
-v ~/.config/aliasman:/root/.config/aliasman \
ghcr.io/patsoffice/aliasman:latestTo run CLI commands inside the container:
docker run --rm \
-v ~/.config/aliasman:/root/.config/aliasman \
ghcr.io/patsoffice/aliasman:latest \
aliasman alias listOverride the entrypoint to use the CLI directly:
docker run --rm --entrypoint aliasman \
-v ~/.config/aliasman:/root/.config/aliasman \
ghcr.io/patsoffice/aliasman:latest \
alias list# CLI
cargo install --path crates/aliasman-cli
# Web frontend
cargo install --path crates/aliasman-webCreate a configuration file at ~/.config/aliasman/config.toml, or run aliasman config
to generate a starter file.
default_system selects which system is used when --system is not specified:
default_system = "home"Each system pairs a storage provider with an email provider, and can set a default domain and email addresses. SQLite is the simplest storage option, using a local database file:
[systems.home]
domain = "example.com"
email_addresses = ["person@example.com"]
[systems.home.storage]
type = "sqlite"
db_path = "~/.config/aliasman/home.db"
[systems.home.email]
type = "rackspace"
user_key = "your-api-user-key"
secret_key = "your-api-secret-key"You can define multiple named systems (e.g. "home" and "work") and switch between them
with --system:
[systems.work]
domain = "work.com"
email_addresses = ["me@work.com"]
[systems.work.storage]
type = "sqlite"
db_path = "~/.config/aliasman/work.db"
[systems.work.email]
type = "rackspace"
user_key = "your-work-api-user-key"
secret_key = "your-work-api-secret-key"S3 storage uses the standard AWS credential chain (environment variables,
~/.aws/credentials, IAM roles, etc.) so no credentials need to be stored in the config
file:
[systems.s3-example.storage]
type = "s3"
bucket = "my-aliasman-bucket"
region = "us-east-1"Static credentials can be provided for S3-compatible services like MinIO or LocalStack, including a custom endpoint:
[systems.s3-local.storage]
type = "s3"
bucket = "aliasman-bucket"
region = "us-east-1"
endpoint = "http://localhost:9000"
access_key_id = "minioadmin"
secret_access_key = "minioadmin"PostgreSQL storage is suitable for shared or container-based deployments:
[systems.prod.storage]
type = "postgres"
url = "postgres://user:pass@host/dbname"Use --system work to target a specific system, or omit it to use default_system.
Create an alias with a random name:
aliasman alias create -d example.com -D "company.com" -r -e person1@example.com -e person2@example.comOutput:
Created alias 5f888d1272833b09@example.com -> person1@example.com, person2@example.com
List all aliases:
aliasman alias listEdit an alias's email addresses or description:
# Change the description
aliasman alias edit -a 5f888d1272833b09 -D "new description"
# Change the target email addresses
aliasman alias edit -a 5f888d1272833b09 -e newperson@example.com
# Change both
aliasman alias edit -a 5f888d1272833b09 -e newperson@example.com -D "new description"Delete an alias:
aliasman alias delete -a 5f888d1272833b09 -d example.comSuspend an alias (stops email routing but preserves metadata):
aliasman alias suspend -a 5f888d1272833b09 -d example.comUnsuspend an alias (restarts email routing):
aliasman alias unsuspend -a 5f888d1272833b09 -d example.comPreview any mutation without making changes:
aliasman --dry-run alias create -r -D "test"
aliasman --dry-run alias edit -a 5f888d1272833b09 -D "new description"
aliasman --dry-run alias delete -a 5f888d1272833b09Search aliases with a regular expression (matches against alias, domain, email addresses, and description):
aliasman alias search -s "shopping"
# Show only suspended aliases
aliasman alias search --exclude-enabled
# Show only active aliases
aliasman alias search --exclude-suspended
# Combine a search pattern with a filter
aliasman alias search -s "example\\.com" --exclude-suspendedAudit aliases by comparing storage against the email provider:
# Audit the default domain
aliasman audit
# Audit a specific domain
aliasman audit -d example.comReports three types of differences:
- MISSING FROM EMAIL — alias is active in storage but not on the email provider
- MISSING FROM STORAGE — alias exists on the email provider but is not tracked in storage
- ADDRESS MISMATCH — alias exists in both but the target email addresses differ
Suspended aliases are expected to be absent from the email provider and are not flagged.
Use a specific system:
aliasman --system work alias listStart the web server:
aliasman-webThis starts the web UI at http://127.0.0.1:3000 using your existing
~/.config/aliasman/config.toml. The UI provides:
- Alias table displaying
alias@domainwith search and filtering (powered by HTMX) - Create aliases with an inline form and random name generator
- Edit alias email addresses and descriptions inline
- Suspend, unsuspend, and delete aliases with per-row action buttons
- System switcher dropdown for multi-system configs
- Hide suspended / hide enabled toggles
- Manual refresh button and automatic 60-second polling
Options:
# Custom config directory
aliasman-web --config-dir /path/to/config
# Custom bind address
aliasman-web --bind 0.0.0.0:8080Convert aliases between storage systems:
# Convert from SQLite to S3
aliasman storage convert --source home --destination s3-example
# Convert from legacy Go S3 format to new S3 format
aliasman storage convert --source legacy-s3 --destination s3-new --legacy-source
# Convert from S3 to SQLite
aliasman storage convert --source s3-example --destination homeThe --legacy-source flag enables reading from the legacy Go S3 format (metadata stored in S3 object headers). This is useful for migrating from the original Go implementation of aliasman.
Full help is available for all commands and subcommands with --help.
SQLite is the default storage provider, storing aliases in a local database file.
The new Rust-native S3 format stores aliases as JSON objects with the following structure:
- Object key:
alias-{alias}@{domain}(e.g.,alias-shopping@example.com) - JSON body: Contains all alias fields with Rust naming conventions
alias: The alias namedomain: The domainemail_addresses: Array of email addressesdescription: Description textsuspended: Boolean flagcreated_at: RFC3339 timestampmodified_at: RFC3339 timestampsuspended_at: RFC3339 timestamp ornull
- Index object: An
indexobject stores a JSON array of all aliases for fast loading
The original Go implementation stored aliases differently:
- Object key: Same format (
alias-{alias}@{domain}) - Object body: Empty (0 bytes)
- Metadata headers: All data stored in S3 object metadata
alias,domain,description,email_addresses(comma-separated)suspended("true"/"false")created_ts,modified_ts,suspended_ts(RFC3339)
- Go zero time:
"0001-01-01T00:00:00Z"represents unset timestamps
Use --legacy-source flag when converting from the old format.
GitHub Actions workflows handle continuous integration and releases:
- CI (
ci.yml) — Runscargo fmt --check,cargo clippy, andcargo teston every push and pull request tomaster - Release (
release.yml) — Triggered by version tags (v*). Builds and pushes a Docker image to GHCR, and attaches CLI binaries for x86_64 Linux and aarch64 macOS to the GitHub release
To create a release:
git tag v0.1.0
git push --tags- Additional CLI commands — sync, sync-from-email
- Additional providers — files storage, Google Workspace email
- Web authentication — Login flow with RBAC (role-based access control) for multi-user deployments