consult-mu icon indicating copy to clipboard operation
consult-mu copied to clipboard

Consult Mu4e asynchronously in GNU Emacs

#+include: ~/OrgFiles/armin/org-macros.setup #+OPTIONS: h:1 num:nil toc:nil d:nil

#+TITLE: consult-mu - use consult to search mu4e dynamically or asynchronously #+AUTHOR: Armin Darvish #+LANGUAGE: en

#+html: Armin Darvish #+html: GNU Emacs

  • About consult-mu Consult-mu provides a dynamically updated search interface to mu4e. It uses the awesome package [[https://github.com/minad/consult][consult]] by [[https://github.com/minad][Daniel Mendler]] and [[https://github.com/djcb/mu][mu/mu4e]] by [[https://github.com/djcb/][Dirk-Jan C. Binnema,]] and optionally [[https://github.com/oantolin/embark][Embark]] by [[https://github.com/oantolin][Omar Antolín Camarena]] to improve the search experience of mu4e.

** Main Interactive Commands There two main interactive commands:

  1. =consult-mu-dynamic=: Provides a dynamic version of =mu4e-search=. As the user types inputs, the result gets updated. This command uses a modified version of =mu4e-search= and then takes the content of =mu4e-headers= buffer to populate minibuffer completion table. This allows the user to change the query or search properties (such as number of results, sort-field, sort-direction, ...) dynamically by changing the input in the minibuffer. In addition previews of the results can be viewed similar to other consult functions. Once a candidate is selected, the user will see the search result sin =mu4e-headers=, and =mu4e-view= buffers similar to mu4e-search results.

  2. =consult-mu-async=: This function provides a very fast search without loading mu4e-headers, which means mu4e functionalities (like marks, reply, forwards, etc.) are not available in the preview buffer. This is very useful for finding individual emails or threads in a large pool quickly (in other words "a needle in a haystack" scenarios!). Previews can be seen while the results are being populated asynchronously (without populating mu4e-headers buffer). Upon selection of a candidate, mu4e-headers buffer is populated with only an individual email (or thread). From here all the normal functionalities of mu4e is again available.

  3. =consult-mu=: This is simply a wrapper that calls =consult-mu-default-command=, which can be set to either the dynamic or async command for quick access. In other words, set =consult-mu-default-command= to either 1 or 2 above depending on your use case and then use =consult-mu= for convinience and you don't need to remember the difference between the two anymore.

The advantage of =consult-mu-async= over =consult-mu-dynamic= is that it is very fast for searching several thousands of messages (even faster than [[https://github.com/emacsmirror/consult-notmuch][consult-notmuch]]!), but cannot populate a mu4e-headers buffer with all the results. On the other hand, =consult-mu-dynamic= is slower when there are thousands of hits for the search term but provides full functionality for all the results (provides full functionality of =mu4e-search=). Therefore, depending on the use case, the user can chose which functions serves the purpose better.

Furthermore, =consult-mu=, also provides a number of useful [[https://github.com/oantolin/embark][Embark]] actions that can be called from within minibuffer (see examples below). However, when using embark actions, be advised that sometimes you may get an error especially when using =consult-mu-async=. These are likely not critical errors hapening when the databse is out of sync with the results. In such cases syncing the database should resolve the issue.

  • Getting Started ** Installation Before you start, make sure you understand that this is work in progress in its early stage and bugs and breaks are very much expected.

note that: Because [[https://github.com/djcb/mu][mu4e]] tends to take over buffer/windows management, I had to reimplement (a.k.a. hack) some of the functionalities in order to provide quick previews that stay out of your way when the minibuffer command is done (or canceled), and as a result there is a good chance that errors will arise in edge cases that I have not tested.

*** Requirements In order to use consult-mu, you need the following requirements:

**** [[https://github.com/djcb/mu][mu4e]]:

You can access the official documentation for mu4e here: [[https://www.djcbsoftware.nl/code/mu/mu4e/][mu4e official manual]]. If you need step-by-step instructions or prefer videos, there are many useful tutorials online. Here are a few good links:

  • EmacsWiki: [[https://www.emacswiki.org/emacs/mu4e][EmacsWiki: mu4e]]
  • SystemCrafters Videos: [[https://www.youtube.com/watch?v=yZRyEhi4y44][Streamline Your E-mail Management with mu4e - Emacs Mail - YouTube]] & [[https://www.youtube.com/watch?v=olXpfaSnf0o][Managing Multiple Email Accounts with mu4e and mbsync - Emacs Mail - YouTube]]
  • Setting Mu4e on MacOS: [[https://macowners.club/posts/email-emacs-mu4e-macos/][Email setup in Emacs with Mu4e on macOS | macOS & (open-source) Software]]

**** [[https://github.com/minad/consult][consult]]:

To install consult follow the official instructions here: [[https://github.com/minad/consult#configuration][Configuration of Consult.]]

Also, make sure you review Consult's README since it recommends some other packages and useful configurations for different settings. Some of those may improve your experience of consult-mu as well. In particular, the section about [[https://github.com/minad/consult#asynchronous-search][asynchronous search]] is important for learning how to use inputs to search for result and narrow down in minibuffer.

*** Installing consult-mu package consult-mu is not currently on [[https://elpa.gnu.org/packages/consult.html][ELPA]] or [[https://melpa.org/#/consult][MELPA]]. Therefore, you need to install it using an alternative non-standard package manager such as [[https://github.com/radian-software/straight.el][straight.el]] or use manual installation.

**** straight.el To install consult-mu with straight.el you can use the following command. Make sure you load consult-mu after loading mu4e and consult (e.g. =require consult=, =require mu4e=).

#+begin_src emacs-lisp (straight-use-package '(consult-mu :type git :host github :repo "armindarvish/consult-mu" :branch "main" :files (:defaults "extras/*.el"))) #+end_src

or if you use =use-package= macro with straight, you can do:

#+begin_src emacs-lisp (use-package consult-mu :straight (consult-mu :type git :host github :repo "armindarvish/consult-mu" :files (:defaults "extras/*.el")) :after (mu4e consult) ) #+end_src

You can also fork this repository and use your own repo.

**** manual installation Clone this repo and make sure the files are on your load path, as described on [[https://www.emacswiki.org/emacs/LoadPath][EmacsWiki]].

Make sure you load consult and mu4e (e.g. =require consult=, =require mu4e=) before you load consult-mu.

** Configuration consult-mu is built with the idea that the user should be able to customize everything based on their use-case, therefore the user is very much expected to configure consult-mu.

I recommend you read through this section and understand how to configure the package according to your needs and for your specific use-case, but if you just want a drop-in minimal config, look at the snippet below (for snippet with extended settings see [[id:99667231-6F96-4913-834F-7C031E3CC44C][extended feature config]]).

*** Minimal Config #+begin_src emacs-lisp (use-package consult-mu :straight (consult-mu :type git :host github :repo "armindarvish/consult-mu" :branch "main") :after (consult mu4e) :custom ;;maximum number of results shown in minibuffer (consult-mu-maxnum 200) ;;show preview when pressing any keys (consult-mu-preview-key 'any) ;;do not mark email as read when previewed (consult-mu-mark-previewed-as-read nil) ;;do not amrk email as read when selected. This is a good starting point to ensure you would not miss important emails marked as read by mistake especially when trying this package out. Later you can change this to t. (consult-mu-mark-viewed-as-read nil) ;; open the message in mu4e-view-buffer when selected. (consult-mu-action #'consult-mu--view-action) ) #+end_src

*** Extended Feature Config :PROPERTIES: :ID: 99667231-6F96-4913-834F-7C031E3CC44C :END:

Here is a customization that gives you the full feature experience including utilities for attaching/detaching files and searching contacts, etc.

#+begin_src emacs-lisp (use-package consult-mu :straight (consult-mu :type git :host github :repo "armindarvish/consult-mu" :branch "develop" :files (:defaults "extras/.el")) :after (consult mu4e) :custom ;;maximum number of results shown in minibuffer (consult-mu-maxnum 200) ;;show preview when pressing any keys (consult-mu-preview-key 'any) ;;do not mark email as read when previewed. If you turn this to t, be aware that the auto-loaded preview if the preview-key above is 'any would also get marked as read! (consult-mu-mark-previewed-as-read nil) ;;mark email as read when selected. (consult-mu-mark-viewed-as-read t) ;;use reply to all when composing reply emails (consult-mu-use-wide-reply t) ;; define a template for headers view in minibuffer. The example below adjusts the width based on the width of the screen. (consult-mu-headers-template (lambda () (concat "%f" (number-to-string (floor ( (frame-width) 0.15))) "%s" (number-to-string (floor (* (frame-width) 0.5))) "%d13" "%g" "%x")))

:config ;;create a list of saved searches for quick access using histroy-next-element' with M-n' in minibuffer. Note the "#" character at the beginning of each query! Change these according to (setq consult-mu-saved-searches-dynamics '("#flag:unread")) (setq consult-mu-saved-searches-async '("#flag:unread")) ;; require embark actions for marking, replying, forwarding, etc. directly from minibuffer (require 'consult-mu-embark) ;; require extra module for composing (e.g. for interactive attachment) as well as embark actions (require 'consult-mu-compose) (require 'consult-mu-compose-embark) ;; require extra module for searching contacts and runing embark actions on contacts (require 'consult-mu-contacts) (require 'consult-mu-contacts-embark) ;; change the prefiew key for compose so you don't open a preview of every file when selecting files to attach (setq consult-mu-compose-preview-key "M-o") ;; pick a key to bind to consult-mu-compose-attach in embark-file-map (setq consult-mu-embark-attach-file-key "C-a") (setq consult-mu-contacts-ignore-list '("^.*no.reply.")) (setq consult-mu-contacts-ignore-case-fold-search t) (consult-mu-compose-embark-bind-attach-file-key) ;; choose if you want to use dired for attaching files (choice of 'always, 'in-dired, or nil) (setq consult-mu-compose-use-dired-attachment 'in-dired) )

#+end_src

*** Customization Variables The following customizable variables are provided:

**** main ***** =consult-mu-default-command= A command function that is called when =M-x consult-mu= is called. This is useful for defining special conditions to use =consult-mu-dynamics= or =consult-mu-async=. By default it is bound to =consult-mu-dynamics=, therefore =M-x consult-mu= simply calls =consult-mu-dynamics=.

***** =consult-mu-headers-buffer-name= This is the default name for HEADERS buffer explicitly for consult-mu. It is, by default, set to ="consult-mu-headers"=. Note that currently, the header-buffer name is a constant string and shared between all instances of consult-mu calls. This is to prevent running operations in parallel that cause out-of-sync issues.

***** =consult-mu-view-buffer-name= This is the default name for VIEW buffer explicitly for consult-mu. It is, by default, set to ="consult-mu-view"=. Note that currently, the view-buffer name is a constant string and shared between all instances of consult-mu calls. This is to prevent creating too many preview buffers and executing operations (such as marking) in parallel that cause out-of-sync issues.

***** =consult-mu-args= This is the default name of the =mu= command line argument. It is set to ="mu"= by default, but can be modified for example if mu is at a different path on your system.

***** =consult-mu-maxnum= Maximum number of messages shown in search results (in consult-mu minibuffer completion table). This is a global option for consult-mu and consult-mu-async, but can be overriden by providing command line arguments in input for example, the following search would fetch up to 1000 results

#+begin_example #github -- --maxnum 1000 #+end_example

***** =consult-mu-headers-fields= This variable is used to format the headers inside minibuffer. This takes a similar format to =mu4e-headers-fields= and changes the format of the header only in minibuffer (=consult-mu-dynamic= or =consult-mu-async=).

Note that it is generally recommended to use =consult-mu-headers-template= below because it has more options and does not affect the consult-mu-headers buffer.

***** =consult-mu-headers-template= :PROPERTIES: :ID: 155CF359-63D0-4D4D-B0BD-7C089E2D9B3C :END: This is a template string that overrides the consult-mu-headers-field to format headers. It provides more options than =consult-mu-headers-field= and is generally the recommended approach to make custom headers. Special chaacters in the string (either “%[char]" or “%[char][integer]”) in the string get expanded to create headers. Each character represents a different field and the integer defines the length of the field. For exmaple "%d15%s50" means 15 characters for date and 50 charcters for subject.

The list of available fields are:

%f sender(s) (e.g. from: field of email) %t receivers(s) (i.e. to: field of email) %s subject (i.e. title of email) %d date (i.e. the date email was sent/received) with the format "Thu 09 Nov 23" %p priority %z size %i message-id (as defined by mu) %g flags (as defined by mu) %G pretty flags (this uses mu4e~headers-flags-str to pretify flags) %x tags (as defined by mu) %c cc (i.e. cc: field of the email) %h bcc (i.e. bcc: field of the email) %r date chaged (as defined by :changed in mu4e)

For example, the string ="%d13%s50%f17%G"= would make a header containing =13= characters for =date=, =50= characters for =subject=, and =20= characters for =from= field, making headers that looks like this: #+attr_org: :width 800px :height nilpx #+attr_latex: :width 800px :height nilpx #+attr_html: :width 800px :height nilpx [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-headers-template.png]]

***** =consult-mu-search-sort-field= This defines the field that is used for sorting the results (refer to documentation on the variable =mu4e-search-sort-field= for more info). It has to be one of the keywords:

  • =:date= sort by date
  • =:subject= sort by title of the email
  • =:size= sort by file size
  • =:prio= sort by priority
  • =:from= sort by name/email of the sender(s)
  • =:to= sort by name/email of receivers
  • =:list= sort by mailing list

Note that the sort field can dynamically be changed by providing command line arguments in the minibuffer input.

For example the following input in the minibuffer will search for emails that are flagged unread but then overrides the sort field and change it t =subject=. For details on how to use command line arguments refer to mu manual (e.g. by running =mu find --help= in the command line)

#+begin_example #flag:unread -- -s s #+end_example

***** =consult-mu-search-sort-direction= Direction of sort. It can either be ='ascending= for A->Z (low number to high number) or ='descending= for Z->A (high number to low number). Note that if a command line argument for reverse order (either -z or --reverse) is provided in the minibuffer, the order will be reverse of the setting defined by this variable.

For example, if =consult-mu-search-sort-field= is set to =:date= and =consult-mu-search-sort-direction= is set to ='descending= the messages are sortes chronologically from the newest on top to the oldest. Then providing a reverse order argument in the minibuffer can dynamically reverse the sort direction:

For example the following input in the minibuffer searches for all =unread= emails under ="./inbox"= path then reverses the sort direction (because of =-z= command line argument) #+begin_example #(maildir:/inbox) AND flag:unread -- -z #+end_example

***** =consult-mu-search-threads= This variable determines whether threads are calculated for search results or not similar to the =mu4e-search-threads= variable.

Note that per mu4e docs: When threading is enabled, the headers are exclusively sorted chronologically (:date) by the newest message in the thread.

When this variable is set to nil, it can still be truned on by adding command line arguments (i.e. =-t= or =--thread=) in the input. For example the following input in the minibuffer will ensure that threads are on even if =consult-mu-search-threads= is set to nil.

#+begin_example #flag:unread -- -t #+end_example

***** =consult-mu-group-by= This variable determines what field is used to group messages. This aloows quick movement between groups (For example with =vertico-next-group= if you use [[https://github.com/minad/vertico][vertico]])

By default it is set to :date. But can be any of the following keywords:

  • =:subject= group by mail title
  • =:from= group by name/email of the sender(s)
  • =:to= group by name/email of the receiver(s)
  • =:date= group by date in the format "Thu 09 Nov 23"
  • =:time= group by the time of email in the format "20:30:07"
  • =:datetime= group by date and time of the email with the format "2023-11-04 08:30:07 PM"
  • =:year= group by the year of the email (i.e. 2023, 2022, ...)
  • =:month= group by the month of the email (i.e. Jan, Feb, ..., Dec)
  • =:week= group by the week number of the email (.i.e. 1, 2, 3, ..., 52)
  • =:day-of-week= group by the day email was sent (i.e. Monday, Tuesday, ...)
  • =:size= group by the file size of the email
  • =:flags= group by flags (as defined by mu)
  • =:tags= group by tags (as defined by mu)
  • =:changed= group by the date changed (as defined by :changed field in mu4e)

Note that grouping works alongside sorting. For example if =consult-mu-group-by= is set to =:day-of-week= and =consult-mu-search-sort-field= is set to =:date=, then the messages are grouped by day of week (e.g. all emails on Tuesdays will be in one group) then ordered chronologically within each group. The screenshot below shows some examples: #+attr_org: :width 800px :height nilpx #+attr_latex: :width 800px :height nilpx #+attr_html: :width 800px :height nilpx [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-grouping-example.png]]

***** =consult-mu-mark-previewed-as-read= This determines whether a message is marked as =read= when it is simply previewed (see =consult-mu-preview-key= above and documentation on =consult-preview-key=).

Note that when =consult-mu-preview-key= is set to ='any=, then as soon as =consult-mu= or =consult-mu-async= retrieve some results a preview for the first message is shown and this can be marked as read if =consult-mu-mark-previewed-as-read= is set to t.

***** =consult-mu-mark-viewed-as-read= This determines whether a message is marked as =read= when it is viewed (i.e. when the minibuffer candidate is selected by hitting =RET=).

***** =consult-mu-preview-key= This is similar to =consult-preview-key= but only for =consult-mu=. By default, it is set to the value of consult-preview-key to keep consistent experience across different consult packages, but you can set this variable explicitly for consult-mu.

The recommended option is to set this to ='any= so as you naviagte over the candidates you see previews updated.

#+begin_src emacs-lisp (setq consult-mu-preview-key 'any) #+end_src

If the option above slows down your system, and you only want to load previews on demand, then you can set it to a specific key such as ="M-o"=.

#+begin_src emacs-lisp (setq consult-mu-preview-key "M-o") #+end_src

You can also turn previews off by setting this variable to =nil=, but this is not generally recommended.

***** =consult-mu-highlight-matches= This variable determines if consult-mu highlights search queries in minibuffer or preview buffers. By default it is set to t.

When it is set to t, all matches of the search term are highlighted in the minibuffer allowing you to notice why the email is a search hit.

For example in the screenshot below, I am searching for the term =consult= and all the matches of consult are highlighted in the titles. #+attr_org: :width 800px :height nilpx #+attr_latex: :width 800px :height nilpx #+attr_html: :width 800px :height nilpx [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-highlight-matches-minibuffer.png]]

Furthermore if I look at a preview, all the instances of consult-gh matches in the preview buffer are also highlighted: #+attr_org: :width 800px :height nilpx #+attr_latex: :width 800px :height nilpx #+attr_html: :width 800px :height nilpx [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-highlight-matches-preview.png]]

Note that when the candidate is selected (i.e. by pressing =RET=), the highlight overlay is turned off, so you can see the orignial message as is, but you can call =consult-mu-overlays-toggle= (i.e. =M-x consult-mu-overlays-toggle=) to see the highlights of the query again. #+attr_org: :width 800px :height nilpx #+attr_latex: :width 800px :height nilpx #+attr_html: :width 800px :height nilpx [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-highlight-matches-view.png]]

***** =consult-mu-action= This variable stores the function that is called when a message is selected (i.e. =RET= is pressed in the minibuffer). By default it is bound to =consult-mu--view-action= which opens both the headers buffer and view buffer and shows the content of the selected message.

**** compose These variables are only available after loading the compose module; #+begin_src emacs-lisp (require 'consult-mu-compose) #+end_src

***** =consult-mu-compose-use-dired-attachment= This variable defines, whether =consult-mu= uses dired buffers for selecting files to attach. If it is set to ='always=, consult-mu will always jump to a dired buffer for selecting files to attach. It it is set to ='in-dired=, consult-mu only uses dired (a.k.a. file at point or marked files) for attaching files when already inside a dired buffer. If it is set to =nil=, consult-mu uses minibuffer completion for file selection for attachments. ***** =consult-mu-large-file-warning-threshold= A threshold to make sure very large files are not accidentally opened when previewing files that the user wants to attach to an email. If file is larger than this threshold, the user is asked to confirm before loading the file buffer.

***** =consult-mu-compose-preview-key= This is similar to =consult-mu-preview-key= but only for =consult-mu-compose=. This is used to preview files when selecting files for attachments. By default, it is set to the follow the value of =consult-mu-preview-key= to keep consistent experience across the package. *But it is recommended to change this to something other than ='any= becuase otherweise every file is previewd as the user is navigating through folders to select files to attach to an email.

#+begin_src emacs-lisp (setq consult-mu-compose-preview-key "M-o") #+end_src

***** =consult-mu-embark-attach-file-key= This variable defines a key that can be bound to =consult-mu-compose-attach= in =emabrk-file-map=, therefore one can call embark on any file and use this key to attach it to an email. This can be bound to "a" or "C-a":

#+begin_src emacs-lisp (setq consult-mu-embark-attach-file-key "C-a") #+end_src

Running the function =consult-mu-compose-embark-bind-attach-file-key= binds this key, but any other key can be passed to this function as well to override the customization variable:

#+begin_src emacs-lisp (consult-mu-compose-embark-bind-attach-file-key) #+end_src

**** contacts These variables are only available after loading the contacts module; #+begin_src emacs-lisp (require 'consult-mu-contacts) #+end_src

***** =consult-mu-contacts-group-by= This variable determines what field is used to group contacts, which allows quick movement between groups (For example with =vertico-next-group= if you use [[https://github.com/minad/vertico][vertico]])

By default it is set to name. But can be any of the following keywords:

  • =:name= group by contact name
  • =:email= group by email of the contact
  • =:domain= group by the domain of the contact's email (e.g. domain.com in [email protected])
  • =:user= group by the ncontact's user name (e.g. user in [email protected])

For example if =consult-mu-contacts-group-by= is set to =:domain= then the domain of the email address is used to group contacts. This is useful for example if you are looking for all emails from a specific company!

***** =consult-mu-contacts-action= This variable stores the function that is called when a contact is selected (i.e. =RET= is pressed in the minibuffer). By default it is bound to =consult-mu-contacts--list-messages-action= which searches for all the messages from that contact by calling =consult-mu=. You can also set this action to other functions such =consult-mu-contacts--insert-email-action= or =consult-mu-contacts--copy-email-action= for inserting or copying the email from contact.

Note that the default action for =consult-mu-contacts-embark= is inserting the email. This is useful for quickly adding contacts when composing messages. You cna also use =embark-collect= or =embark-export= to select multiple contacts and act on all of them (e.g. insert all in "To:" field in a compose buffer).

***** =consult-mu-contacts-ignore-list= This is a list of rgexp that gets ignored when searching contacts. This is useful to filter certain invalid addreses (e.g. "no-reply" addresses from contacts). For example you can remove no-reply adresses by setting this variable as follows; #+begin_src emacs-lisp (setq consult-mu-contacts-ignore-list '("^.*no.reply.$"))

#+end_src

***** =consult-mu-contacts-ignore-case-fold-search= This variable defines whether =consult-mu-contacts= uses case insensitive search when matching against =consult-mu-contacts-ignore-list= (see above). if you set this to =t=, it will preform case insensitive match.

  • Features and Demos For a detailed description of why this package is useful, and some useful screenshosts showing work flows, you can read my blog post here:

[[https://www.armindarvish.com/post/improve_your_mu4e_workflow_with_consult-mu/]]

** Search consult-mu uses [[https://github.com/minad/consult#asynchronous-search][consult's asynchronous search]] feature. In order to search a query you can type your standard Mu4e query after =#= sign in the minibuffer. Here are some examples:

#+begin_example #tickets #flag:unread #(flag:unread AND maildir:/inbox) #(maildir:/drafts OR maildir:/sent) #+end_example

In addition, you can pass command line arguments that you can normally pass to =mu= (see =mu find --help=) in the command line to consult-mu by using a =--= separator like this:

#+begin_example #tickets -- --maxnum 500 -s s #+end_example

In the example above, the maximum number of results to retrieve are set to 500 (=--maxnum 500=) and the result are sorted by the subject (=-s s=). For more details on command line options, you can refer to mu's help (i.e. run =mu find --help= in terminal).

Once consult-mu retrieves a list of results, you can further narrow down the list of candidates by using a second =#=.

#+begin_example #tickets -- -z #concert #+end_example

In the example above, we first run a search for tickets and sort the results with reverse order (using =-z= option) then narrow down the list of candidates by the word, concert.

Note that narrow downs are similar to other narrow downs in the minibuffer, therefore here you cannot use mu4e search syntax but you can use regular expressions. Also, the narrow down searches in the entire header string, therefore you can use any information available in the headers (date, title, email addresses, flags,...) for narrow down. (to change the format of the header, take a look at [[id:155CF359-63D0-4D4D-B0BD-7C089E2D9B3C][=consult-mu-headers-template=]]. Here is a screenshot for doing a search and narrow:

#+ATTR_ORG: :width 800px #+ATTR_LATEX: :width 800px #+ATTR_HTML: :width 800px [[https://github.com/armindarvish/consult-mu/blob/screenshots/screenshots/consult-mu-search-narrow.gif]]

** Saved Searches and Quick Access History consult-mu does have a history variable and keeps record of your previous searches. These searches can quickly be accessed using =previous-history-element= (bound to =M-p= by default). In addition, you can have a list of saved searches for quick access by modifying the variable =consult-mu-saved-searches-dynamic= or =consult-mu-saved-searches-async= for =consult-mu-dynamic= and =consult-mu-async=, respectively. These are lists of mu4e query strings that get added to future-history when you run consult-mu-dynamic or consult-mu-async. By default, =consult-mu-saved-searches-async= is set to inherit from =consult-mu-saved-searches-dynamic=, but you can modify them independently. This allows separating saved searches that are more suitable for async search from those that are better done with dynamic collection.

Similarly =consult-mu-compose-attach=, =consult-mu-compose-detach=, and =consult-mu-contacts= have their own history elements as wels future history elemetns (for example email in the current message buffer gets added to future-history for searching contacts).

** Integration with Embark consult-mu provides integration with [[https://github.com/oantolin/embark][embark]] defined in =consult-mu-embark.el= If you use embark, you can use these commands to run actions on the search results directly from minibuffer. For example, you can call =consult-mu-embark-reply= to reply to a message or =consult-mu-embark-mark-for-refile= to refile (a.k.a. archive) the message and so on.

For marking, note that, by default when consult-mu-embark is loaded, it creates a function for every item in the =mu4e-marks= and binds them to the =:char= for that mark in =consult-mu-embark-messages-actions-map=.

You can also use =embark-select= and =embark-act-all= to run the same command on multiple messages, but this only works in consult-mu-dynamic and not so well with consult-mu-async because with consult-my-async, only one message at a time is added to the headers view and therefore running commands on multiple candidates with embark is not supported.

** Extras (attachments, contacts, ...) In addition to improvements for searching, consult-mu also provides extra features for composing, contacts, ... These features are defined in the files under the =extras= folder. *** Attachments By default, attaching files to emails with mu4e is not very intuitive or interactive. If you use [[https://github.com/jeremy-compostella/org-msg][org-msg]], it provides an interactive command for attaching files, but it is only for one file at a time and for each file you need to go through some menus. Some emacs configs, like [[https://github.com/doomemacs/doomemacs][doomemacs]], do provide their own commands to allow using a dired buffer to attach multiple files to an email (see [[https://github.com/doomemacs/doomemacs/blob/986398504d09e585c7d1a8d73a6394024fe6f164/modules/email/mu4e/autoload/email.el#L272C8-L272C26][+mu4e/attach-files]]). Personally, I prefer using a minibuffer completion (instead of switching to a dired buffer) unless I am already in a dired buffer. The [[file:extras/consult-mu-compose.el][consult-mu-compose]] provides the utilities and customization settings to achieve interactive multi-file attachment.

To use attachment extras, you need to load the compose module by: #+begin_src emacs-lisp (require 'consult-mu-compose) (require 'consult-mu-compose-embark) ;;optionally load embark actions for compose #+end_src

It is highly recommended that you also change the preview key binding for =consult-mu-compose= to something other than ='any= because seeing a preview of every file when you are navigating through the minibuffer may be slow and annoying. It's easier to get a preview of the files you are interested in with a key binding like =M-o= than to see a preview of every file.

#+begin_src emacs-lisp (setq consult-mu-compose-preview-key "M-o") #+end_src **** Attaching Files =consult-mu-compose-attach= provides an interactive command to attach files to an email and it tries its best to provide an intuitive interface.

Here are some features:

  • interactively selecitng draft compose buffer: In a compose buffer (e.g. =mu4e-compose-mode=, =message-mode=, =org-msg-edit-mode=), it assumes that the user wants to attach files to the current message. In other buffers it asks the user to select a current compose buffer or creates a new one if noe exists.

  • interactively selecting files: For selecting files, it can either use minibuffer completion or a dired buffer. If the customization variable =consult-mu-compose-use-dired-attachment= is set to =always=, it always uses dired buffer similar to what dom emacs does. If it is set to ='in-dired= it only uses dired when inside a dired-mode buffer. and if it is set to =nil=, it will always use minibuffer completion. When using dired to chose files, one can mark multiple files to attach to a message. Furthermore, if the command =consult-mu-compose-attach= is called from within a compose buffer (e.g. =mu4e-compose-mode=, =message-mode=, =org-msg-edit-mode=), runing =embrak-dwim= on the file will attach the file to the current message. This is specially useful if you use =embark-dwim= with =no-quit= option ([[https://github.com/oantolin/embark#quitting-the-minibuffer-after-an-action][embark#quitting-the-minibuffer-after-an-action]]). Here is an example on how to define =embark-dwim-noquit=:

#+begin_src emacs-lisp (defun embark-dwim-noquit () "Run action but don't quit the minibuffer afterwards." (interactive) (let ((embark-quit-after-action nil)) (embark-dwim))) #+end_src Then you can just attach multiple files to the same message from any folder by using the minibuffer completion. See the screenshot in my blog post here: https://www.armindarvish.com/post/improve_your_mu4e_workflow_with_consult-mu/

**** Removing attachments: Similarly, =consult-mu-compose-detach= provides an interactive command to remove files that are already attached to the current message. It uses minibuffer completion to select a file to remove. Similar to what mentioned above, embark-dwim (or a no-quit version of it) can be used to remove multiple files from the same message. note that the minibuffer completion list does not get dynamically updated when a file is removed (This is something I may improve in the future but for now it works just fine I think). See the screenshot in my blog post here: https://www.armindarvish.com/post/improve_your_mu4e_workflow_with_consult-mu/

*** Contacts =consult-mu-contacts= provides an interactive way to search mu contacs. By default, when a candidate is selected, all the emails form the contact is searched by using =consult-mu=. This can be customized to oyher functions (e.g. compose an email to the contact) by seeting =consult-mu-contacts-action=. Note that mu does not have a stored list of contacts. Rather, contacts are generated dynamically from the email fields in all messages in the database, including email fields that might be invalid! To use =consult-mu-contacts=, you need to load the contacts module by:

#+begin_src emacs-lisp (require 'consult-mu-contacts) (require 'consult-mu-contacts-embark) ;;optionally load embark actions for contacts #+end_src

See the screenshot in my blog post here: https://www.armindarvish.com/post/improve_your_mu4e_workflow_with_consult-mu/

  • Bug reports To report bug, first check if it is already reported in the [[https://github.com/armindarvish/consult-mu/issues][issue tracker]] and see if there is an existing solution or add relevant comments and discussion under the same issue. If not file a new issue following these steps:
  1. Make sure the dependencies are installed, and both =mu4e= and =consult= work as expected.

  2. Remove the package and install the latest version (along with dependencies) and see if the issue persists.

  3. In a bare bone vanilla Emacs (>=28) (e.g. =emacs -Q=), install the latest version of consult-mu (and its dependencies) without any configuration or other packages and see if the issue still persists.

  4. File an issue and provide important information and context in as much detail as possible in your bug report. Important information can include:

  • Your operating system, version of Emacs (or the version of emacsen you are using), version of mu/mu4e and consult (see [[https://github.com/emacsorphanage/pkg-info][pkg-info]]).
  • The installation method and the configuration you are using with your consult-mu.
  • If there is an error message, turn debug-on-error on (by =M-x toggle-debug-on-error=) and include the backtrace content in your report.
  • If the error only exists when you have some other packages installed, list those packages (e.g. problem happens when evil is installed)
  • It would be useful, if you can look at consult-mu buffers while the minibuffer command is active (by default they are named =consult-mu-headers= and =consult-mu-view= buffers) and report whether theya re getting populated properly or not.
  • Contributions This is an open source package, and I appreciate feedback, suggestions, ideas, etc. There are lots of functionalities that can be added to this package to improve different user's workflows, so if you have some ideas, feel free to file an issue for a feature request.

If you want to contribute to the code, please note that the main branch is currently stable (as stable as a work in progress like this can be) and the develop branch is the current work in progress. So, start from the develop branch to get the latest work-in-progress updates and create a new branch with names such as feature/name-of-the-feature or fix/issue, ... Do the edits and then create a new pull request to merge back with the develop branch when you are done with your edits.

Importantly, keep in mind that I am using a literate programming approach (given that this is a small project with very limited number of files) where everything goes into consult-mu.org and then gets tangled to appropriate files (for now that includes consult-mu.el and consult-mu-embark.el). If you open a pull-request where you directly edited the .el files, I will likely not approve it because that will then get overwritten later when I tangle from the .org file. In other words, Do Not Edit The .el Files! only edit the .org file and tangle to .el files.

  • What about other packages? Why we need a new package such as consult-mu? While mu4e's built-in search is great and provides ways to edit search terms (e.g. =mu4e-search-edit=) or toggle search properties (e.g. =mu4e-search-toggle-property=), the interface is not really intuitive. We are simply too impatient in 2024 to use a static search field and edit in steps. A dynamically updated search results is somewhat expected with any modern tools. Try searching in Thunderbird, or Outlook, and you instantly see suggestions and results. This is what consult-mu provides. Hopefully, in the future a dynamics search approach comes built-in with mu4e but until then, this package intends to fill the gap.

*** What about alternative approaches like [[https://github.com/seanfarley/counsel-mu][counsel-mu]] or [[https://github.com/emacsmirror/consult-notmuch][consult-notmuch]]?

You can read my blog post for some thoughts on this: https://www.armindarvish.com/post/improve_your_mu4e_workflow_with_consult-mu/

Both [[https://github.com/seanfarley/counsel-mu][counsel-mu]] and [[https://github.com/emacsmirror/consult-notmuch][consult-notmuch]] inspired this package. But this package provides soemthing more than those packages. Here is the comparison:

  • [[https://github.com/seanfarley/counsel-mu][counsel-mu]]: counsel-mu provides an async search for mu, and when the candidate is selected, the single message is loaded by using =mu4e-view-message-with-message-id=. =consult-mu= takes a similar approach in =consult-mu-async= but expands on that with the following features:

    a. ability to dynamically add command line options (e.g. using =query -- -s s -z= as input). This includes dynamically changing the number of results. This is important because with counsel-mu, one ha to change the variable =mu4e-search-results-limit= globally to see many results, which then affects every mu4e-search that is done.

    b. ability to load a preview without leaving minibuffer search

    c. ability to use emabrk actions on candidates from the minibuffer with =consult-mu-embark=

    d. ability to see whole threads when selecting a candidate rather than just single messages

    In addition, =consult-mu= provides the =consult-mu-dynamic= interactive command that uses dynamic collection using =mu4e-search= (and not =mu= commands). This allows doing dynamic search in the minibuffer and then getting a full list of results similar to the built-in mu4e-search.

  • [[https://github.com/emacsmirror/consult-notmuch][consult-notmuch]]: consult-notmuch is great IF you use [[https://notmuchmail.org/notmuch-emacs/][notmuch-emacs]]. consult-mu on the other hand provides similar functionality for [[https://github.com/djcb/mu][mu/mu4e]]. The comparison therefore comes down to comparing mu4e and notmuch. While notmuch is light and fast, I think it lacks lots of basic functionalities of an email client. It is supposed to be not much after all! As a result, using notmuch as an everyday email client can be challenging especially if you want to use it along with other IMAP-based email clients (e.g. mobile apps). This is because the philosophy of using tags instead of folders requires a complete redesign of some workflows. While some services, like Gmail, use labels, others may not and even if they do, in IMAP-based clients labels are treated as different folders and therefore syncing back custom notmuch labels everywhere (e.g. between notmuch on your desktop machine and your mobile app client) becomes tricky. Of course, in Emacs nothing is impossible and there are ways to improve the experience by adding additional custom elisp (see [[https://www.youtube.com/watch?v=g7iF11qamh8][Emacs: Notmuch demo (notmuch.el) - YouTube]] and [[https://www.reddit.com/r/emacs/comments/qo3eza/notmuch_as_an_alternative_to_mu4e/?share_id=8WUviRO3gGGIO4bQgoSzU&utm_content=2&utm_medium=android_app&utm_name=androidcss&utm_source=share&utm_term=10][Notmuch as an alternative to mu4e : emacs]] for some examples), but at the end, the results will still likely lack some important features (like multi-account contexts, ...).

More importantly, a common reason to choose notmuch over mu4e is its speed, but using the underlying =mu= server and command line can also be very fast. In fact, at least in my own tests, =consult-mu-async=, which uses the command line =mu= commands was faster than =consult-notmuch=. With the latest release of mu4e (version 1.12), even =consult-mu-dynamics= (a.k.a. built-in mu4e-search) is now very fast. Therefore, using =mu4e= with =consult-mu= provides the best of both worlds to me. When I need speed and simplicity, I can use =consult-mu-async= to do a fast search (and find thousands of hits) and quickly narrow down to what I am looking for; and when I need a more complete and full-feature client I can use =mu4e= built-in functionalities, and with addition of dynamically built searches with =consult-mu-dynamic=, I have a modern intuitive interface as well.

  • Acknowledgments Obviously this package would not have been possible without the fabulous [[https://github.com/djcb/mu][mu/mu4e]], and [[https://github.com/minad/consult][consult]] packages. It also took inspiration from other packages including but not limited to [[https://github.com/seanfarley/counsel-mu][counsel-mu]], [[https://github.com/emacsmirror/consult-notmuch][consult-notmuch]], and [[https://github.com/doomemacs/doomemacs][doomemacs]].