     [file version.texi not included]

Copyright © 2025-UPDATED-YEAR Dirk-Jan C. Binnema

     Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.  A copy of the license is included in the section entitled ``GNU Free Documentation License.''

Mu-SCM Manual
*************

Welcome to mu-scm!

mu is a program for indexing and searching your e-mails. It can do so in many different ways, but sometimes that may not be enough.

mu-scm is made for such cases. It embeds the Guile programming language into mu. Guile is the GNU Ubiquitous Intelligent Language for Extensions - a version of the Scheme programming language, the official GNU extension language, and a member of the Lisp family of programming languages -- like emacs-lisp, Racket, Common Lisp.

mu-scm is replacing the older mu-guile bindings; some notable differences are:

  * No separate 'module', instead use mu itself: This greatly reduces the number of 'moving parts' and mysterious errors for users

  * Automatically set up a reasonable environment: mu scm simply reuses the user's mu configuration, simplifying setup

  * API improvements: mu-scm has learned from mu-guile to make its APIs nicer to use

  * However, some parts are still missing: mu-scm does not yet support all that mu-guile did. It's just getting started.

If you're not familiar with Scheme or Lisp, mu-scm may be a fun way to learn a bit more! Note: mu-scm is brand new and rather experimental for now, and APIs can still change without warning.

Getting started
***************

This chapter walks you through the installation and basic setup.

Using distributions
===================

At the time of writing, no distributions ship with an SCM-enabled mu yet, so for now you need to build it yourself. Of course, this is fully optional.

Building it yourself
====================

To build mu with SCM support, first you need to ensure you have installed the required Guile development packages. The details of getting those vary across environments / distributions, e.g.: on Fedora (as root):

     # dnf install guile30-devel

or on Debian/Ubuntu:

     $ sudo apt install guile-3.0-dev

With those packages in place, you can (re)build mu and mu-scm should be built automatically if you did not explicitly disable it.

Parts of mu-scm depend on mu being installed, not just built; however, you can still use it un-installed as well by setting an environment variable MU_SCM_DIR to the source-directory, e.g. /home/user/sources/mu/scm.

Verifying support
=================

After installing mu, you can check the output of mu info. If mu-scm is available, in the table you should find a line:

     | scm-support      | yes     | GNU Guile 3.x support (new)?          |

Shell
*****

This chapter discusses the mu-scm-powered shell.

After installation (see Getting started), you can start the mu-scm shell by issuing:

     $ mu scm

This is the Guile REPL (https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) customized for mu. In particular, it supports all the common mu command-line arguments for specifying where your mu stores its data and so on (see the mu and mu scm man-pages for details).

     $ mu scm
     [....]
     Welcome to the mu shell!

     GNU Guile 3.0.9
     Copyright (C) 1995-2023 Free Software Foundation, Inc.

     Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
     This program is free software, and you are welcome to redistribute it
     under certain conditions; type `,show c' for details.

     Enter `,help' for help.
     scheme@(guile-user)>

This shell is set-up for use with mu with the module imported and the message database loaded. So we can try some simple queries, using the mfind function, which mimics mu find (It is called mfind instead of find to avoid clashing with the core Guile function by that name.)

     scheme@(guile-user)> (mfind "rhinoceros")
     $1 = (#<<message> 7f2aa671fce0>)

Which means we found one message matching rhinoceros. If you don't have such a message, try some different query. We can then inspect the message, using the $1 temporary:

     scheme@(guile-user)> (subject (car $1))
     $2 = "Important message about African animals"

Scripts
*******

In the Shell chapter, we saw how you can use mu-scm interactively. In this also possible to run scripts. Generally, you can invoke a script by passing its name to mu scm, for example:

     mu scm path/to/script/myscript.scm

mu-scm expects scripts to have a procedure main of the form:

 -- Scheme Procedure: main script #:rest args

main receives the script (path) as its first argument, and any other arguments in the list args.

Thus, an example script might look like:

     (use-modules (mu))

     (define* (main script #:rest args)
       (format #t "running script ~a and arguments ~s\n" script args)
       (for-each
        (lambda (msg)
          (format #t "~a ~a\n"
                  (time-t->iso-date (date msg))
                  (or (subject msg) "No subject")))
        (mfind "hello AND date:2015.." #:max-results 5)))

You can run it like this:

     $ mu scm ~/myscript.scm some args 123
     running script /home/user/myscript.scm and arguments  ("some" "args" "123")
     2015-01-02T12:41:13 Happy 2015! It's Day 2 - How Are Those Resolutions Coming?
     2015-01-06T12:29:21 Brunch Sunday 11.1.
     2015-01-06T18:05:27 Moderator's spam report for mu-discuss@googlegroups.com
     2015-01-13T11:37:27 Upvoted: The Story Behind the Users Who Make Reddit's Front Page
     2015-01-16T17:06:23 [mu] combining maildirs in query (#559)

Quite likely, your output will differ from the above.

API Reference with examples
***************************

This chapter goes through the mu-scm API. For this, we need to understand a few key concepts, represented in some GOOP objects and other data-structures:

  * the <store> represents the mu database with information about messages

  * from the store, you can find <message> objects, each of which represent a specific message (similar to what you get from mu find)

  * the store also exposes tha contacts in the store as alists (``association lists'') (similar to what you get from mu cfind)

Store
=====

The store represents the mu database, i.e., the place where mu index stores information about messages and contacts.

While you could theoretically have multiple stores, for now mu-scm only supports a single one, which is the store you opened when you started mu scm. For completeness and possible future use, store-related methods do take a #:store parameter, but you can (in fact, must) leave it out, and use its default value.

Hence, in the API descriptions below, we leave out the #:store argument.

The store currently only exposes a few methods, described below.

 -- Scheme Procedure: mfind query [#:related? #f] [#:skip-dups? #f] [#:sort-field 'date]
     [#:reverse? #f] [#:max-results #f]

Perform a query for messages in the store, and return a list of message objects (see Message) for the matches.

  * QUERY is a Mu query; see the mu-query man-page for details

  * #:RELATED? whether related messages should be included. This is similar to the --include-related parameter for mu find

  * #:SKIP-DUPS? whether to exclude duplicate messages This is similar to the --skip-dups parameter for mu find

  * #:SORT-FIELD a symbol, the message field to sort by You can sort by the fields (see mu info fields that have a value=yes)

  * #:REVERSE? whether to reverse the sort-direction (make it descending)

  * #:MAX-RESULTS the maximum number of results By default all matches are returned

mfind mimics the mu find command-line command.

Example usage:

     (mfind "capybara" #:skip-dups? #t #:sort-field 'subject)
     => (#<<message> 7f3c8ac09c00> #<<message> 7f3c8ac09be0>)

 -- Scheme Procedure: cfind pattern [#:personal? #f] [#:after #f] [#:max-results #f]

Search for contacts in the store, and return a list of contacts for the matches. Each contact is an association list with at least a key (symbol email) with e-mail address as its value, and possibly a name key with the contact's name. In the future, other fields may be added.

  * PATTERN is a basic case-insensitive PCRE-compatible regular expression see the pcre(3) man-page for details

  * #:PERSONAL? if true, only match personal contacts A personal contact is a contact seen in message where ``you'' were an explicit sender or recipient, thus excluding mailing-list. Personal addresses are those that were specified at store creation time - see the mu-init man-page, in particular the --personal-address parameter

  * #:AFTER only include contacts last-seen after some time-point Specified as the number of seconds since epoch. Helper-function iso-date->time-t can be useful here.

  * #:MAX-RESULTS (optional) the maximum number of results By default, all matches are returned

cfind mimics the mu cfind command-line command.

Example usage:

     (car (cfind "smith" #:personal? #t))
     => ((name . "Hannibal Smith") (email . "jhs@example.com"))

 -- Scheme Procedure: mcount

Returns the number of messages in the store.

Example usage:

     (mcount)
     => 140728

 -- Scheme Procedure: store->alist

Retrieve an association list (``alist'') with information about the store.

Example:

     (store->alist)
     => ((batch-size . 50000) (created . 1741180008) (max-message-size . 100000000)
          (personal-addresses "djcb@example.com" "msx@example.com")
          (root-maildir . "/home/user/Maildir") (schema-version . 500))

Message
=======

A message represents the information about an e-mail message. mu gets this information either from its database (the mu store), e.g., with mfind (see Store) or by reading an email message from the file-systems with make-message.

In many of the examples below we assume there is some message object, e.g. as retrieved through:

     (define msg (car (mfind "hello")))

 Many of the procedures below use the internal representation of the message from the database; this re-uses the same information that mu4e uses. However, that is not sufficient for all: body and header need the full message. To get this, it needs to open the message file from the file-system. Much of this is internal to mu-scm, except that full-method-procedures are a bit slower relatively to the database-only ones.

Basics
------

 -- Scheme Procedure: make-message path

Create a new message object from a file-system path.

This is a full message, unlike the ones you get from a store-query (i.e., mfind).

 -- Scheme Procedure: subject message

Get the message subject, or #f if there is none.

For example:

     (subject msg)
     => "Hello!"

 -- Scheme Procedure: maildir message

Get the message subject, or #f if there is none.

For example:

     (maildir msg)
     => "/inbox"

 -- Scheme Procedure: path message

Get the file-system path for the the message.

For example:

     (path msg)
     => "/home/user/Maildir/archive/cur/1546942532.adb906ab91921e10.hyperion:2,DS"

 -- Scheme Procedure: message-id message

Get the message's Message-ID field, or #f if there is none.

For example:

     (message-id msg)
     => "87a15477-dd66-43e5-a722-81c545d6af19@gmail.com"

 -- Scheme Procedure: date message

Get the message's Date field (the sent-date), or #f if there is none. date expressed the data as the number of seconds since epoch, time_t.

As a convenience, iso-date expresses the date as an ISO-8601-compatible string or an empty string of the same length.

For example:

     (date msg)
     => 1750064431
     (iso-date msg)
     => 2025-06-16T09:00:31

 -- Scheme Procedure: body message [#:html? #f]

Get the message body as a string, or return #f if not found.

If #:HTML? is non-#f, get the HTML-body instead.

This requires the full message.

 -- Scheme Procedure: message-id message

Get the message's Message-ID field, or #f if there is none.

For example:

     (message-id msg)
     => "87a15477-dd66-43e5-a722-81c545d6af19@gmail.com"

MIME-parts
----------

Messages consist of one or more MIME-parts, which include the body, attachments and other parts. To get the MIME-parts for a message, you can use the mime-parts method on a message.

 -- Scheme Procedure: mime-parts message

Get the MIME-parts for this message, as a list of <mime-part> objects.

A MIME-parts is an object with a few methods.

 -- Scheme Procedure: mime-part->alist mime-part

Get an association list (alist) describing the MIME part.

For example:

     ;; describe the second MIME-part of the first message with an attachment
     (mime-part->alist
      (cadr (mime-parts
            (car (mfind "flag:attach" #:max-results 1)))))
     => ((filename . "emacs.png") (size . 18188) (content-type . "image/png") (index . 1))

Depending on the MIME-part, different fields can be present:

  * index the index (number) of the part, 0-based

  * mime-type the MIME-type of the part

  * size the size of the part in bytes. For encoded parts, this is the encoded size

  * filename the filename (for attachments). This is as specified in the message, but with forward slashes and control-characters removed or substituted with -.

  * signed? is this part (cryptographically) signed?

  * encrypted? is this part encrypted?

 -- Scheme Procedure: make-port mime-part [#:content-only? #f]
     [#:decode? #t]

Get a read-port for the given MIME-part. Ports are the standard mechanism for dealing with I/O in Guile; see its documentation for further details.

If content-only? is true, only include the contents, not headers. If decode? is true, decode the content (from e.g., Base-64); in that case, content-only? is implied to be #t.

 -- Scheme Procedure: write-to-file [#:filename #f] [#:overwrite? #f]

Write MIME-part to file.

Use filename is the file/path to use for writing; if this is #f, the name using the filename procedure.

If overwrite? is true, overwrite existing files of the same name; otherwise, raise an error if the file already exists.

 -- Scheme Procedure: filename mime-part

Determine a filename for the given MIME-part.

This is either taken from the filename property of the MIME-part alist, or, If that does not exist, a generic name.

Contacts
--------

Message fields To:, From:, Cc: and Bcc: contain contacts. mu-scm represents those as list of contact-alists, or contacts for short. Each contact is an alist with at least an email and optionally a name field. For instance:

     (to msg)
     => (((name . "Hannibal Smith") (email . "jhs@example.com"))
         ((email . "murdock@example.com")))

 -- Scheme Procedure: from message

Get the list of message senders (usually only one). Returns either a list of contacts or #f.

 -- Scheme Procedure: to message

Get the message's intended To: recipients. Returns either a list of contacts or #f if not found.

 -- Scheme Procedure: cc message

Get the message's intended carbon-copy Cc: recipients. Returns either a list of contacts or #f if not found.

 -- Scheme Procedure: bcc message

Get the message's intended blind carbon-copy Bcc: recipients. Returns either a list of contacts or #f if not found.

Flags
-----

Message can have a number of properties or flags.

 -- Scheme Procedure: flags message

Get the message's list of flags. Flags are symbols, see mu info fields for the list of all flags.

For example:

     (flags msg)
     => (draft seen personal)

There are some helpers to check for the presence of specific flags:

 -- Scheme Procedure: flag? message flag

Does the message have the given flag? #t or #f.

For example:

     (flags? msg 'personal)
     => #t
     (flags? msg 'calendar)
     => #f

 -- Scheme Procedure: draft? message

Does the message have the draft flag? Returns #t or #f.

 -- Scheme Procedure: flagged? message

Does the message have the flagged flag? Returns #t or #f.

 -- Scheme Procedure: passed? message

Does the message have the passed flag? I.e., has it been forwarded? Returns #t or #f.

 -- Scheme Procedure: replied? message

Does the message have the replied flag? Returns #t or #f.

 -- Scheme Procedure: seen? message

Does the message have the seen flag? Returns #t or #f.

 -- Scheme Procedure: trashed? message

Does the message have the trashed flag? I.e., has it been marked for removal? Returns #t or #f.

 -- Scheme Procedure: new? message

Is this a new message? Returns #t or #f.

 -- Scheme Procedure: signed? message

Is this a cryptographically signed message? Returns #t or #f.

 -- Scheme Procedure: encrypted? message

Is this an encrypted message? Returns #t or #f.

 -- Scheme Procedure: attach? message

Does the message have an attachment? Returns #t or #f.

 -- Scheme Procedure: unread? message

Is this message unread? I.e., either new or not seen? Returns #t or #f.

 -- Scheme Procedure: list? message

Is this a mailing-list message? Returns #t or #f.

 -- Scheme Procedure: personal? message

Is this a personal message? Returns #t or #f.

 -- Scheme Procedure: calendar? message

Does this message include a calendar invitation? Returns #t or #f.

Miscellaneous
-------------

 -- Scheme Procedure: last-change message

Get the time of the message's last change (through mu), or #f if there is none. The time is expressed the data as the number of seconds since epoch, time_t.

For example:

     (last-change msg)
     => 1703336567

 -- Scheme Procedure: priority message

Get the message's priority. This is a symbol, either high, normal or low, or #f if not present.

For example:

     (priority msg)
     => normal

 -- Scheme Procedure: size message

Get the message's size in bytes.

For example:

     (size msg)
     => 2815

 -- Scheme Procedure: language message

Get the ISO-639-1 language code for message's primary language or #f if not found. This is available only if mu was built with CLD2 support, see mu info. The language code is represented as a symbol, such as en, nl or fi.

For example:

     (language msg)
     => en

 -- Scheme Procedure: header message

Get some arbitrary, raw header from the message.

The HEADER parameter is a case-insensitive string without the colon (:).

This requires the full message.

For example:

     (header msg "subject")
     => "Re: Musical chairs"
     (header msg "From")
     => "\"Raul Endymion\" <raul@example.com>"
     (header msg "Something")
     => #f

 -- Scheme Procedure: references message

Get the list of references (message-ids of related messages) for this message. This combines the References and In-Reply-To fields, from oldest to the immediate parent. Returns #f if there are no references.

For example:

     (references msg)
     => ("439C1136.90504@euler.org" "4399DD94.5070309@euler.org"
         "20051209233303.GA13812@gauss.org" "439B41ED.2080402@euler.org"
         439A1E03.3090604@euler.org" "20051211184308.GB13513@gauss.org")

 -- Scheme Procedure: thread-id message

Get the oldest reference for the message or its message-id if there is none. This is useful to identify the thread some message lives in.

For example:

     (thread-id msg)
     => "439C1136.90504@euler.org"

For example:

     (references msg)
     => ("439C1136.90504@euler.org" "4399DD94.5070309@euler.org"
         "20051209233303.GA13812@gauss.org" "439B41ED.2080402@euler.org"
         439A1E03.3090604@euler.org" "20051211184308.GB13513@gauss.org")

 -- Scheme Procedure: mailing-list message

Get the mailing-list id for this message (corresponding with the List-Id: field), or #f if there is none.

For example:

     (mailing-list msg)
     => "gnu-emacs-sources.gnu.org"

Miscellaneous
=============

 -- Variable: %options

An association-list (alist) of general options passed to mu scm or their default values.

      %options
     => ((mu-home . #f) (quiet . #f) (debug . #f) (verbose . #f))

Helpers
=======

 -- Scheme Procedure: string->time timestr [#:utc? (assoc-ref %preferences 'utc?)]

Convert some ISO-8601-style time-string to a seconds-since-epoch time_t value. TIMESTR is expected to be in the strftime-format %F%T, or a prefix thereof. Non-numerical characters are ignored.

You can influence whether UTC is assumed using the optional #:utc? parameter. The input time/date format is fixed.

 -- Scheme Procedure: time->string
     [#:format (assoc-ref %preferences 'short-date)] [#:utc? (assoc-ref %preferences 'utc?)]

Convert a time_t value (``seconds-since-epoch'') to a string. The optional #:format parameter (an strftime-compatible string) determines the output format, while the #:utc? determines whether to use UTC.

If TIME_T is #f, return #f.

GNU Free Documentation License
******************************

     [file fdl.texi not included]

Procedure Index
***************

This is an alphabetical list of all the public procedures and macros in mu-scm.

Variables Index
***************

This is an alphabetical list of all the public variables mu-scm.