Skip to content

Repository files navigation

BONE — Backlog Of Notable Emails

8==8      <= A bone.

https://img.shields.io/badge/status-active-brightgreen.svg?style=for-the-badge https://img.shields.io/badge/versioning-intver.org-blue.svg?style=for-the-badge

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:

The gnus-gnaw, notmuch-gnaw and mu4e-gnaw front-ends are unmaintained proofs of concept.

BONE manual

For more extensive documentation, read the BONE manual.

For the codebase, architecture and development setup, see CONTRIBUTING.md.

bone-capture.jpg

Quick start

# 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 tasks

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)

Theme (--html-theme / :theme)

The --html-theme flag (or :theme in config.edn) controls the CSS theme for HTML exports. Values are resolved in order:

  1. https:// URL – used as an external <link> stylesheet.
  2. file:///path – local file, inlined in <style>.
  3. Path ending in .css – relative or absolute path, inlined in <style>.
  4. 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

Configuration

See config.edn.example. The configuration has three main sections.

Config file location

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; all bb scripts 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.

Mail sources (:mailboxes)

: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.

FieldRequiredDescription
:nameyesUnique identifier (keys the watermark, logs)
:typeyes:imap or :maildir
:hostIMAP onlyIMAP server hostname
:userIMAP onlyIMAP login username
:passwordIMAP *IMAP password (* or :oauth2-token)
:oauth2-tokenIMAP *OAuth2 token (* or :password)
:pathMaildir onlyRoot path of the Maildir
:foldernoFolder name (default: "INBOX")

See the manual (docs/bone-manual.org § Multiple mailboxes) for the behaviour of batch and watch modes with several mailboxes.

Ingest settings (:ingest)

FieldDefaultDescription
:fetch{:limit 50}First-run fetch: {:limit N} or {:since "30d"} or {:start "…" :end "…"}
:max-sizenoneSkip emails larger than N bytes (e.g. 1048576)
:max-attachment-size1048576Max bytes extracted from attachments (.patch, .ics, etc.)

Sources (:sources)

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:

KeySource typeMatches against
:listMailing listList-Id header (bare identifier, e.g. "bugs.example.org")
:aliasEmail aliasX-Original-To, Envelope-To, X-Envelope-To, or Delivered-To
:toMailboxDelivered-To header
FieldRequiredDescription
:nameyesUnique source identifier
:list / :alias / :toyesExactly one; determines source type and matched header
:commandsnoCommand keyword overrides (per action)
:labelsnoReport label overrides (per report type)
:maintainersnoInitial maintainers; first entry is the lead (see below)
:list-archivenoURL of the list archive (shown in HTML reports)
:archive-format-stringnoURL template for message-ids (%s is replaced)
:base-urlnoBase URL where this source’s public directory is served
:report-typesnoRestrict detected and exported types, e.g. #{:bug :patch}
:export-formatsnoOutput formats (default: ["json" "org" "rss"])
:expirynoAuto-close delays in days per type
:notificationsno{:enabled false} silences notifications for this source
:awaiting-delaynoDuration 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 and the “lead”

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.edn and 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.

Canonical display names (mailmap.edn)

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).

Example config

{: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"}}}

Report labels (:labels)

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:

TypeDefault labels
:bugBUG
:patchPATCH
:requestPOLL FR TODO
:announcementANN ANNOUNCEMENT
:releaseREL RELEASE
:changeCHG 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"]}}

Notifications (:notifications)

Optional. When absent or {:enabled false}, bb notify exits immediately.

FieldRequiredDescription
:enabledyesGlobal kill switch (true / false)
:smtpyesSMTP connection settings (see below)
:admin-bccnoHidden Bcc on every digest (string or vector of email)
:subscribersnoRecipient list with per-source filters

SMTP fields:

FieldRequiredDescription
:hostyesSMTP server hostname
:portyesSMTP port (usually 587)
:tlsnoEnable STARTTLS (true)
:useryesSMTP login username
:passwordyesSMTP password
:fromyesSender address for emails
:reply-tonoReply-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.

Contributing

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.

Intentional Versioning

This project uses Intentional Versioning. The three audiences:

  • Users : end-users and sysadmins who operate BONE instances
  • Integrators : consumers of the exported JSON/RSS/Org formats
  • Maintainers : maintainers of the codebase

Support the Clojure(script) ecosystem

If you like Clojure(script), consider supporting maintainers by donating to clojuriststogether.org.

License

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.

About

Backlog Of Notable Emails

Resources

Contributing

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages