8==8 <= A bone.
BONE is an email-driven report tracker – see the principles.
BONE monitors a single IMAP mailbox or Maildir, classifies incoming emails into sources by header matching, detects report types from subject tags, applies commands from email bodies, and manages roles per source.
BONE reports can be read with other tools:
- As a CLI based on fzf: gnaw
- As a standalone GNU Emacs tool: gnaw.el
- Via GNU Emacs Gnus: gnus-gnaw
- Via GNU Emacs notmuch: notmuch-gnaw
- Via GNU Emacs mu4e: mu4e-gnaw
The gnus-gnaw, notmuch-gnaw and mu4e-gnaw front-ends are
unmaintained proofs of concept.
For more extensive documentation, read the BONE manual.
For the codebase, architecture and development setup, see CONTRIBUTING.md.
# 1. Copy and edit config (minimal = 8 lines; example = annotated reference)
cp config.edn.minimal config.edn
# 2a. Run once (fetch new emails, expire, exit)
clj -M:run
# 2b. Run in watch mode (IMAP IDLE or Maildir, stays running)
clj -M:run -- --watch
# 2c. Or build an uberjar and run it (production)
bb build-jar # produces target/bone.jar + bin/bone wrapper
bin/bone --watch# 3. Export reports bb export # incremental (skips if nothing changed) bb export --force # full rebuild, ignore timestamps bb export root # regenerate top-level public/index.html only bb export --topics-filter "event" # only reports with topic "event" # # Individual formats remain available as positional args: # bb export json | rss | org | html | stats | patches | text | events # 4. Send notification emails bb notify # send notifications bb notify --dry-run # preview without sending bb notify --debug # verbose filtering diagnostics # 5. Test SMTP configuration (dry-run by default; --send to actually send) bb test-smtp # 6. Maintenance bb maintenance --verbose # show orphan emails bb maintenance --delete # actually purge bb maintenance --failures # list recent command failures # 7. Run tests clj -M:test
bb export [--force] Incremental export of all formats
[-n source] [-p 1|2|3] [-s 1-7] Filter by source, priority, status
[--only-open] Also export -open files
[--html-theme THEME] Override CSS theme (see below)
[--topics-filter TOPICS] Only export matching topics (csv)
[--html-page-size N] Paginate HTML table (N rows/page)
[--html-columns COLS] HTML table columns (csv subset)
[--html-columns-sort COL] Default sort column (default: date)
[--closed-retention DUR] Drop reports closed before cutoff
bb export <format> Single format: json|rss|org|html|
stats|patches|text|events|root|all
bb notify [--dry-run] [--debug] Send notification emails
bb maintenance [--delete] [--verbose] [-n source] Purge orphan emails
[--retention DUR] Orphan retention (default 90d)
[--failures] List recent command failures
bb test-config [path] Validate config.edn
bb test-smtp [--to addr] [--send] Test SMTP config (default = dry-run)
bb build-jar Build target/bone.jar + bin/bone wrapper
bb clean Remove all files in public/
clj -M:run -- --fresh Wipe DB + full replay (interactive)
The --html-theme flag (or :theme in config.edn) controls the CSS
theme for HTML exports. Values are resolved in order:
- https:// URL – used as an external
<link>stylesheet. - file:///path – local file, inlined in
<style>. - Path ending in .css – relative or absolute path, inlined
in
<style>. - Bare name – treated as a pico-themes name; base theme and bone overlay are loaded from jsDelivr.
Set to "none" to disable theming.
bb export html --html-theme org # pico-themes name bb export html --html-theme https://example.com/custom.css # external URL bb export html --html-theme file:///home/user/my-theme.css # local file bb export html --html-theme themes/custom.css # relative path bb export html --html-theme none # no theme
See config.edn.example. The configuration has three main sections.
By default BONE reads ./config.edn. To override:
- Pass
-c <path>to the daemon:clj -M:run -- -c /path/to/config.edn - Or set
BONE_CONFIG; allbbscripts honor it.
Precedence: -c > BONE_CONFIG > ./config.edn.
The BONE_DB env var is honored as a fallback when no
:db {:path …} entry is set.
:mailboxes is a vector; each entry is one mail source (IMAP or
Maildir) with a unique :name. Batch mode iterates over them
sequentially; watch mode runs one thread per mailbox. Watermarks are
keyed per :name, so IMAP UID state never mixes between mailboxes.
| Field | Required | Description |
|---|---|---|
:name | yes | Unique identifier (keys the watermark, logs) |
:type | yes | :imap or :maildir |
:host | IMAP only | IMAP server hostname |
:user | IMAP only | IMAP login username |
:password | IMAP * | IMAP password (* or :oauth2-token) |
:oauth2-token | IMAP * | OAuth2 token (* or :password) |
:path | Maildir only | Root path of the Maildir |
:folder | no | Folder name (default: "INBOX") |
See the manual (docs/bone-manual.org § Multiple mailboxes) for the behaviour of batch and watch modes with several mailboxes.
| Field | Default | Description |
|---|---|---|
:fetch | {:limit 50} | First-run fetch: {:limit N} or {:since "30d"} or {:start "…" :end "…"} |
:max-size | none | Skip emails larger than N bytes (e.g. 1048576) |
:max-attachment-size | 1048576 | Max bytes extracted from attachments (.patch, .ics, etc.) |
Each source classifies incoming emails by delivery type. Sources are checked in order; the first match wins. Each source must have exactly one of the three type keys:
| Key | Source type | Matches against |
|---|---|---|
:list | Mailing list | List-Id header (bare identifier, e.g. "bugs.example.org") |
:alias | Email alias | X-Original-To, Envelope-To, X-Envelope-To, or Delivered-To |
:to | Mailbox | Delivered-To header |
| Field | Required | Description |
:name | yes | Unique source identifier |
:list / :alias / :to | yes | Exactly one; determines source type and matched header |
:commands | no | Command keyword overrides (per action) |
:labels | no | Report label overrides (per report type) |
:maintainers | no | Initial maintainers; first entry is the lead (see below) |
:list-archive | no | URL of the list archive (shown in HTML reports) |
:archive-format-string | no | URL template for message-ids (%s is replaced) |
:base-url | no | Base URL where this source’s public directory is served |
:report-types | no | Restrict detected and exported types, e.g. #{:bug :patch} |
:export-formats | no | Output formats (default: ["json" "org" "rss"]) |
:expiry | no | Auto-close delays in days per type |
:notifications | no | {:enabled false} silences notifications for this source |
:awaiting-delay | no | Duration before “awaiting reply” flag, e.g. "14d" (default: 14 days) |
Send all state-changing actions (commands, role controls) through the source’s public channel. Anyone running BONE on the same source then sees the same state.
Maintainers are seeded from the per-source :maintainers vector;
the first entry is the lead maintainer.
- Any active maintainer can run
Add maintainer:commands. - Only the lead maintainer can run
Remove maintainer:commands. - The lead cannot remove themselves. To change the lead, edit
config.ednand redeploy.
Each Add / Remove opens or closes a tenure in the DB – a
[from, to) interval recording when someone held maintainer
status. BONE exports the full history (active and closed) to
meta.json and renders it in docs.html, so a fresh deployment
replays commands consistently even when maintainers have changed.
To unify display names across addresses belonging to the same
person, create mailmap.edn at the repo root:
{"Bastien Guerry" ["bzg@gnu.org" "bzg@bzg.fr"]}BONE applies the mailmap at export time only (the DB is untouched)
to maintainers, authors, and owners. Unlisted emails keep their
last observed From: name.
For sources whose maintainer list or vocabulary evolved over time,
declare the history inline via :periods on the source (see
docs/bone-manual.org §Source periods).
{:mailboxes [{:name "primary"
:type :imap :host "imap.example.com" :port 993 :ssl true
:user "imap-login@example.com" :password "secret"
:folder "INBOX"}]
:sources [{:name "public-list"
:list "bugs.example.org"
;; First entry = lead maintainer.
:maintainers ["lead@example.org" "co-maint@example.org"]
:list-archive "https://lists.example.org/bugs/"}
{:name "team-alias"
:alias "team@example.com"}
{:name "direct-inbox"
:to "inbox@example.com"}]
:db {:path "data/bone-db"}
:ingest {:fetch {:limit 50}
:max-size 1048576}
:notifications {:enabled true
:smtp {:host "smtp.example.com" :port 587 :tls true
:user "notify@example.com" :password "secret"
:from "bone@example.com"}}}Subject labels recognised per report type. Set globally at the top level or per source (per-source wins). Overrides apply only to the types you specify; others keep their defaults:
| Type | Default labels |
|---|---|
:bug | BUG |
:patch | PATCH |
:request | POLL FR TODO |
:announcement | ANN ANNOUNCEMENT |
:release | REL RELEASE |
:change | CHG CHANGE |
Global override:
:labels {:bug ["BUG" "DEFECT"]
:request ["POLL" "FR" "TODO" "RFE"]}Per-source override (inside a source map):
{:name "my-list"
:labels {:announcement ["ANN" "NEWS"]}}Optional. When absent or {:enabled false}, bb notify exits
immediately.
| Field | Required | Description |
|---|---|---|
:enabled | yes | Global kill switch (true / false) |
:smtp | yes | SMTP connection settings (see below) |
:admin-bcc | no | Hidden Bcc on every digest (string or vector of email) |
:subscribers | no | Recipient list with per-source filters |
SMTP fields:
| Field | Required | Description |
|---|---|---|
:host | yes | SMTP server hostname |
:port | yes | SMTP port (usually 587) |
:tls | no | Enable STARTTLS (true) |
:user | yes | SMTP login username |
:password | yes | SMTP password |
:from | yes | Sender address for emails |
:reply-to | no | Reply-To header |
Use bb test-smtp to validate the configuration (no email sent by
default), or bb test-smtp --send to send a test email to the
lead maintainer.
See CONTRIBUTING.md for reporting bugs, sending patches, and working on the codebase.
You can also send me an email and support my work on liberapay.
This project uses Intentional Versioning. The three audiences:
Users: end-users and sysadmins who operate BONE instancesIntegrators: consumers of the exported JSON/RSS/Org formatsMaintainers: maintainers of the codebase
If you like Clojure(script), consider supporting maintainers by donating to clojuriststogether.org.
Copyright © 2026 Bastien Guerry
The Clojure code is distributed under the Eclipse Public License 2.0 and the JavaScript code under the Mozilla Public License 2.0.