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 / mailto commands bundling "template → $EDITOR → compile → validate/re-edit/view/abort", and interpret (aliased read) for the inverse MIME→MML flow.

Table of contents

Features

  • MML → MIME compilation (requires compiler feature):
    • <#part> / <#multipart> directives with type, 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 interpreter feature):
    • Header include / exclude filters
    • Part include / exclude filters
    • HTML → text rendering via nanohtml2text
    • Attachment save-to-disk
    • mml interpret (aliased mml 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

Sponsoring

nlnet

Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:

This program is part of Pimalaya, free software funded entirely by grants and donations. If you find it useful, consider sponsoring its development:

GitHub Ko-fi Buy Me a Coffee Liberapay thanks.dev PayPal