MML [](https://docs.rs/mime-meta-language/latest/mml) [](https://matrix.to/#/#pimalaya:matrix.org) [](https://fosstodon.org/@pimalaya) [](https://pimalaya.org/sponsor/)
August 15, 2026 · View on GitHub
CLI and lib for the Emacs MIME message Meta Language (MML), written in Rust.
This repository ships two layers:
- Low-level library exposing two pipelines (MML→MIME compiler, MIME→MML interpreter) and a template builder for compose/reply/forward drafts.
- High-level CLI wrapping the library, plus editor-driven
compose/reply/forward/mailtocommands bundling "template →$EDITOR→ compile → validate/re-edit/view/abort", andinterpret(aliasedread) for the inverse MIME→MML flow.
Table of contents
Features
- MML → MIME compilation (requires
compilerfeature):<#part>/<#multipart>directives withtype,filename,disposition,encoding,description,name,recipient-filename, dates, etc.- Inline parts, attached parts, nested multiparts (
alternative,mixed,related) - File-path expansion via
shellexpand - MIME-type detection via
tree_magic_mini - Parse-error reporting via
ariadne(CLI)
- MIME → MML interpretation (requires
interpreterfeature):- Header include / exclude filters
- Part include / exclude filters
- HTML → text rendering via
nanohtml2text - Attachment save-to-disk
mml interpret(aliasedmml read): MIME on stdin, MML/text on stdout
- Editor-driven flow (requires
cli+compiler+interpreter):mml compose/mml reply/mml forward: open$EDITOR, compile on save, prompt to validate / re-edit / view / abort
- TOML configuration with per-account identities and per-section defaults (
[compose],[reply],[forward],[read])
Tip
MML is written in Rust and uses cargo features to gate functionality. The default feature set is declared in Cargo.toml.
Installation
Pre-built binary
The CLI binary mml can be installed from the latest GitHub release using the install script:
As root:
curl -sSL https://raw.githubusercontent.com/pimalaya/mml/master/install.sh | sudo sh
As a regular user:
curl -sSL https://raw.githubusercontent.com/pimalaya/mml/master/install.sh | PREFIX=~/.local sh
For a more up-to-date version, check out the pre-releases GitHub workflow: pick the latest run and grab the artifact matching your OS. These are built from the master branch.
Note
Pre-built binaries are built with the default cargo features. If you need a different feature set, use another installation method.
Cargo
cargo install mime-meta-language --locked --features cli
You can also use the git repository for a more up-to-date (but less stable) version:
cargo install --locked --git https://github.com/pimalaya/mml.git
To use mml as a library, add it to your Cargo.toml:
[dependencies]
mime-meta-language = { version = "1.1", default-features = false, features = ["compiler", "interpreter"] }
Drop cli (and pick only compiler and/or interpreter) for a slim library build with no clap, no ariadne, no editor integration.
Nix
If you have the Flakes feature enabled:
nix profile install github:pimalaya/mml
Or run without installing:
nix run github:pimalaya/mml -- compile <<<'<#part>Hello, world!<#/part>'
Sources
git clone https://github.com/pimalaya/mml
cd mml
nix run
Configuration
A sample config.sample.toml is shipped at the repository root. Drop it into one of:
- $XDG_CONFIG_HOME/mml/config.toml
- $HOME/.config/mml/config.toml
- $HOME/.mmlrc
Override the path with -c <PATH> or MML_CONFIG=<PATH>.
CLI flags always win; config values fill in the blanks. Pick an account with -a <NAME>, or flag one entry default = true.
Usage
Library
See documentation at docs.rs.
CLI
Every command documents itself: mml --help lists them, mml <command> --help details one. A few real-world lines follow.
Every command reads its source from stdin and writes its artifact to stdout by default. -i, --input <PATH> reads a file instead, -o, --output <PATH> writes one, and - names the stream explicitly in either (use ./- for a file actually called -). --output is independent of --json: the first picks where the payload lands, the second picks its format, so --json --output result.json writes JSON to that file.
Compile MML on stdin, emit MIME on stdout. The source is a full message: headers, a blank line, then the MML body.
printf 'From: me@example.org\nTo: you@example.org\nSubject: Hi\n\n<#part>Hello, world!<#/part>' | mml compile
Interpret MIME back to MML or text:
mml interpret --input message.eml
Open the editor on a fresh compose draft, then emit the compiled MIME message:
mml compose --from me@example.org
mml compose --from me@example.org --output /tmp/draft.eml
Reply and forward read the source MIME the same way and route the result the same way:
cat message.eml | mml reply --all
mml forward --input message.eml --output /tmp/draft.eml
Pipelines into himalaya v2:
mml compose --from me@example.org --output /tmp/draft.eml && himalaya messages send /tmp/draft.eml
mml compose --from me@example.org --output >(himalaya messages send)
himalaya messages read 42 | mml reply --output >(himalaya messages send)
An editor-driven command needs stdout to stay on the terminal, because the editor it spawns inherits that stream. Writing to a path, whether a real file or a process substitution, satisfies that. The bare-pipe form mml compose | himalaya messages send cannot work, and mml now refuses it with an error naming the fix instead of hanging.
Read (MIME → text), useful for piping through less or chaining with himalaya:
cat message.eml | mml read --exclude-header Received,DKIM-Signature
Sign and encrypt with PGP, when built with the gpg feature. The properties go on the part, and signing always runs before encrypting:
mml compile <<< '<#part sign=pgpmime sender=me@example.org>hello<#/part>'
mml compile <<< '<#part encrypt=pgpmime recipients=you@example.org,other@example.org>secret<#/part>'
Keys, the agent and the passphrase prompt all belong to your GPG installation, so recipients resolve through the same auto-key-locate the gpg command uses. Reading decrypts and verifies the same way. Without the feature, a message asking to be encrypted fails to compile rather than going out in the clear, and an encrypted part reads as an unresolved marker.
Compose from a mailto: URI, which is what a desktop handler registers:
mml mailto 'mailto:you@example.org?subject=Hi&body=Hello'
mml mailto "\$1" --output /tmp/draft.eml && himalaya messages send /tmp/draft.eml
Generate a draft template without opening the editor:
mml template compose --from me@example.org
mml template reply --all < message.eml
mml template forward < message.eml
Plug mml into himalaya v2 by wiring shell pipelines or aliases against the messages send / messages add / messages read primitives. A typical .bashrc snippet:
hsend() { local f=$(mktemp --suffix=.eml); mml compose -o "$f" && himalaya messages send "$f"; rm -f "$f"; }
hreply() { himalaya messages read "\$1" | mml reply -o >(himalaya messages send); }
FAQ
How to debug the CLI?
Use --log <level> where <level> is one of off, error, warn, info, debug, trace:
mml --log trace compile < message.mml
The RUST_LOG environment variable, when set, overrides --log and supports per-target filters (see the env_logger documentation). RUST_BACKTRACE=1 enables full error backtraces, including source lines where the error originated from.
Logs are written to stderr, so they can be redirected easily to a file:
mml --log trace compile < message.mml 2>/tmp/mml.log
How does `mml compose` pick the editor?
The edit crate resolves $VISUAL first, then $EDITOR, then an OS default. mml does not expose a config knob on top: set VISUAL / EDITOR in your shell rc file.
License
This project is licensed under either of:
Social
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Sponsoring
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2026 → 2027: NGI Zero Commons Fund
This program is part of Pimalaya, free software funded entirely by grants and donations. If you find it useful, consider sponsoring its development:
