Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages


home | help
AFEW(1)				     afew			       AFEW(1)

       afew - afew Documentation

       afew is an initial tagging script for notmuch mail:



       Its  basic  task	 is to provide automatic tagging each time new mail is
       registered with notmuch.	In a classic setup, you	might  call  it	 after
       notmuch new in an offlineimap post sync hook or in the notmuch post-new

       It can do basic thing such as adding tags based	on  email  headers  or
       maildir folders,	handling killed	threads	and spam.

       fyi: afew plays nicely with alot, a GUI for notmuch mail	;)



       The steps to get	up and running are:

       o install the afew package

       o create	the config files

       o add a notmuch post-new	hook that calls	afew

       The following commands will get you going on Debian/Ubuntu systems:

	  $ sudo aptitude install notmuch python-notmuch dbacl
	  $ git	clone git://
	  $ cd afew
	  $ python install --prefix

       Ensure that ~/.local/bin	is in your path. One way is to add the follow-
       ing to your ~/.bashrc:

	  if [ -d ~/.local/bin ]; then

       See installation	for a more detailed guide.

   Initial Config
       Make sure that ~/.notmuch-config	reads:


       Put a list of filters into ~/.config/afew/config:

	  # This is the	default	filter chain

       And create a post-new hook for notmuch.

	  $ mkdir -p path/to/maildir/.notmuch/hooks
	  $ touch path/to/maildir/.notmuch/hooks/post-new

       Then edit the post-new file to contain:

	  $HOME/.local/bin/afew	--tag --new

   Next	Steps
       You can:

       o add extra filters for more custom filtering

       o make use of the move_mode to move your	email between folders

       o run afew against all your old mail by running afew _atag _aall

       o start extending afew

       afew works with python 3.6+, and	requires notmuch and its python	 bind-
       ings.  On Debian/Ubuntu systems you can install these by	doing:

	  $ sudo aptitude install notmuch python-notmuch python-dev python-setuptools

       Note:  if you are installing notmuch using Homebrew on macOS, make sure
       to run $	brew install --with-python3 notmuch, because the brew  formula
       doesnat install python3 notmuch bindings	by default.

   Unprivileged	Install
       It  is recommended to install afew itself inside	a virtualenv as	an un-
       privileged user,	either via checking out	the source code	and installing
       via, or	via pip.

	  # create and activate	virtualenv
	  $ python -m venv --system-site-packages .venv
	  $ source .venv/bin/activate

	  # install via	pip from PyPI:
	  $ pip	install	afew

	  # or install from source:
	  $ python install --prefix=~/.local

       You  might  want	 to  symlink .venv/bin/afew somewhere inside your path
       (~/bin/ in this case):

	  $ ln -snr .venv/bin/afew ~/.bin/afew

   Building documentation
       Documentation can be built in various formats using Sphinx:

	  # build docs into build/sphinx/{html,man}
	  $ python build_sphinx -b html,man

       Ultimately afew is a command line tool.	You have to specify an action,
       and  whether  to	act on all messages, or	only on	new messages.  The ac-
       tions you can choose from are:

       tag    run the tag filters.  See	Initial	tagging.

       watch  continuously monitor the mailbox for new files

	      move mail	files between maildir folders

   Initial tagging
       Basic tagging stuff requires no configuration, just run

	  $ afew --tag --new
	  # or to tag *all* messages
	  $ afew --tag --all

       To do this automatically	you can	 add  the  following  hook  into  your

	  postsynchook = ionice	-c 3 chrt --idle 0 /bin/sh -c "notmuch new && afew --tag --new"

       There  is  a lot	more to	say about general filter configuration and the
       different filters provided by afew.

       Adding _adry-run to any _atag or _async-tags action	prevents  modification
       of the notmuch db. Add some -vv goodness	to see some action.

   Move	Mode
       To invoke afew in move mode, provide the	_amove-mails option on the com-
       mand line.  Move	mode will respect _adry-run, so throw in	 _averbose  and
       watch what effects a real run would have.

       In  move	 mode,	afew will check	all mails (or only recent ones)	in the
       configured maildir folders, deciding whether they should	 be  moved  to
       another folder.

       The  decision  is based on rules	defined	in your	config file. A rule is
       bound to	a source folder	and specifies a	target	folder	into  which  a
       mail will be moved that is matched by an	associated query.

       This  way  you will be able to transfer your sorting principles roughly
       to the classic folder based maildir structure understood	by your	tradi-
       tional  mail server. Tag	your mails with	notmuch, call afew _amove-mails
       in an offlineimap presynchook and enjoy a clean inbox in	your webinter-
       face/GUI-client at work.

       Note  that  in  move  mode,  afew  calls	notmuch	new after moving mails
       around.	You can	use afew -m _anotmuch-args=_ano-hooks in a pre-new  not-
       much hook to avoid loops.

       For  information	 on how	to configure rules for move mode, what you can
       do with it and what you canat, please refer to move_mode.

   Commandline help
       The full	set of options is:

	  $ afew --help
	  Usage: afew [options]	[--] [query]

	    -h,	--help		  show this help message and exit

	      Please specify exactly one action.

	      -t, --tag		  run the tag filters
	      -w, --watch	  continuously monitor the mailbox for new files
	      -m, --move-mails	  move mail files between maildir folders

	    Query modifiers:
	      Please specify either --all or --new or a	query string.

	      -a, --all		  operate on all messages
	      -n, --new		  operate on all new messages

	    General options:
	      -C NOTMUCH_CONFIG, --notmuch-config=NOTMUCH_CONFIG
				  path to the notmuch configuration file [default:
				  $NOTMUCH_CONFIG or ~/.notmuch-config]
	      -e ENABLE_FILTERS, --enable-filters=ENABLE_FILTERS
				  filter classes to use, separated by ',' [default:
				  filters specified in afew's config]
	      -d, --dry-run	  don't	change the db [default:	False]
	      -R REFERENCE_SET_SIZE, --reference-set-size=REFERENCE_SET_SIZE
				  size of the reference	set [default: 1000]
	      -T DAYS, --reference-set-timeframe=DAYS
				  do not use mails older than DAYS days	[default: 30]
	      -v, --verbose	  be more verbose, can be given	multiple times

   Configuration File
       Customization of	tag filters takes  place  in  afewas  config  file  in

   NotMuch Config
       afew  tries to adapt to the new tag that	notmuch	sets on	new email, but
       has mostly been developed and used against the new tag.	To  use	 that,
       make sure that ~/.notmuch-config	contains:


       afew  reads  the	notmuch	database location from notmuch config. When no
       database	path is	set in notmuch config, afew uses the MAILDIR  environ-
       ment  variable  when set, or $HOME/mail as a fallback, like notmuch CLI
       does. If	a relative path	is provided, afew prepends $HOME/ to the  path
       in  the same manner as notmuch, which was introduced in version 0.28 of

   Filter Configuration
       You can modify filters, and define your own versions of the base	Filter
       that allow you to tag messages in a similar way to the notmuch tag com-
       mand, using the config file.  The default config	file is:

	  sent_tag = ''

       See the filters page for	the details of those filters  and  the	custom
       arguments they accept.

       You  can	 add  filters  based on	the base filter	as well.  These	can be
       customised by specifying	settings beneath them.	The standard settings,
       which apply to all filters, are:

	      text  that  will	be  displayed while running this filter	if the
	      verbosity	is high	enough.

       query  the query	to use against the  messages,  specified  in  standard
	      notmuch format.  Note that you donat need	to specify the new tag
	      -	afew will add that when	run with the _anew flag.

       tags   the tags to add or remove	for messages  that  match  the	query.
	      Tags  to add are preceded	by a + and tags	to remove are preceded
	      by a -.  Multiple	tags are separated by semicolons.

	      if the message has one of	these tags, donat add tags to it. Tags
	      are separated by semicolons.

       So  to add the deer tag to any message to or from you
       could do:

	  query	= ''
	  tags = +deer
	  message = Wild animals ahoy

       You can also (in	combination with the InboxFilter) have email skip  the
       Inbox by	removing the new tag before you	get to the InboxFilter:

	  query	= from''
	  tags = -new;+boss
	  message = Message from above

   Full	Sample Config
       Showing	some  sample  configs  is  the easiest way to understand.  The
       notmuch initial tagging page shows a sample config:

	  # immediately	archive	all messages from "me"
	  notmuch tag -new -- tag:new and

	  # delete all messages	from a spammer:
	  notmuch tag +deleted -- tag:new and

	  # tag	all message from notmuch mailing list
	  notmuch tag +notmuch -- tag:new and

	  # finally, retag all "new" messages "inbox" and "unread"
	  notmuch tag +inbox +unread -new -- tag:new

       The (roughly) equivalent	set up in afew would be:


	  message = Delete all messages	from spammer
	  query	=
	  tags = +deleted;-new

	  message = Tag	all messages from the notmuch mailing list
	  query	=
	  tags = +notmuch


       Not that	the queries do not generally include tag:new because  this  is
       implied when afew is run	with the _anew flag.

       The differences between them is that

       o the ArchiveSentMailsFilter will add tags specified by sent_tag	option
	 (default _a_a means add no tags.	You may	want to	set it	to  sent),  as
	 well  as  archiving the email.	And it will not	archive	email that has
	 been sent to one of your own addresses.

       o the InboxFilter does not add the unread tag.  But most	 mail  clients
	 will manage the unread	status directly	in maildir.

   More	Filter Examples
       Here are	a few more example filters from	github dotfiles:

	  query	= ''
	  tags = +sicsa
	  message = sicsa

	  query	= ' OR from:GT Silber OR'
	  tags = +soc;+foo
	  message = foosoc

	  query	= 'folder:gmail/G+'
	  tags = +G+
	  message = gmail spam

	  # skip inbox
	  query	= ' AND (subject:emacs OR subject:elisp OR "(defun" OR "(setq" OR PATCH)'
	  tags = -new
	  message = notmuch emacs stuff

	  # Assuming the following workflow: all messages for projects or releases should be tagged
	  # as "project/A", "project/B"	respectively "release/1.0.1" or	"release/1.2.0".
	  # In most cases replies to messages retain their context: the	project, the release(s), ..
	  # The	following config will propagate	all project/...	or release/... tags from a thread
	  # to all new messages.

	  propagate_tags = project/.*
	  # do not tag spam
	  filter = not is:spam

	  propagate_tags = release/.*

       The  default  filter  set (if you donat specify anything	in the config)


       The standard filter configuration can be	applied	to  these  filters  as
       well.  Though  note  that most of the filters below set their own value
       for message, query and/or tags, and some	ignore some  of	 the  standard

       It extends SentMailsFilter with the following feature:

	  o Emails  filtered  by this filter have the new tag removed, so will
	    not	have the inbox tag added by the	InboxFilter.

       This filter verifies DKIM signatures of E-Mails with DKIM  header,  and
       adds dkin-ok or dkin-fail tags.

       DMARC  reports  usually come in ZIP files. To check the report you have
       to unpack and search thru XML document which is very tedious. This fil-
       ter tags	the message as follows:

       if  thereas  any	 SPF  failure  in any attachment, tag the message with
       admarc-spf-faila	tag, otherwise tag with	admarc-spf-oka

       if thereas any DKIM failure in any attachment,  tag  the	 message  with
       admarc-dkim-faila tag, otherwise	tag with admarc-dkim-oka

       For each	email, it looks	at all folders it is in, and uses the path and
       filename	as a tag, for the email.  So if	you have a procmail  or	 sieve
       set up that puts	emails in folders for you, this	might be useful.

       o folder_explicit_list =	<folder	list>

	  o Tag	mails with tag in <folder list>	only. <folder list> is a space
	    separated list, not	enclosed in quotes or any other	way.

	  o Empty list means all folders (of course blacklist still applies).

	  o The	default	is empty list.

	  o You	may use	it e.g.	to set tags only  for  specific	 folders  like

       o folder_blacklist = <folder list>

	  o Never  tag	mails  with  tag  in <folder list>. <folder list> is a
	    space separated list, not enclosed in quotes or any	other way.

	  o The	default	is to blacklist	no folders.

	  o You	may use	it e.g.	to avoid mails being tagged  as	 aINBOXa  when
	    there is the more standard ainboxa tag.

       o folder_transforms = <transformation rules>

	  o Transform  folder  names  according	 to the	specified rules	before
	    tagging mails.  <transformation rules> is a	space  separated  list
	    consisting	of  afolder:taga  style	pairs. The colon separates the
	    name of the	folder to be transformed from the  tag	it  is	to  be
	    transformed	into.

	  o The	default	is to transform	to folder names.

	  o You	 may  use  the rules e.g. to transform the name	of your	aJunka
	    folder into	your aspama tag	or fix capitalization  of  your	 draft
	    and	sent folder:

	  folder_transforms = Junk:spam	Drafts:draft Sent:sent

       o folder_lowercases = true

	 o Use lowercase tags for all folder names

       o maildir_separator = <sep>

	  o Use	<sep> to split your maildir hierarchy into individual tags.

	  o The	default	is to split on a.a

	  o If your maildir hierarchy is represented in	the filesystem as col-
	    lapsed dirs, <sep> is used to split	it again before	applying tags.
	    If your maildir looks like this:


       the mails in your afew folder will be tagged with adevela and aafewa.

       If  instead  your  hierarchy is split by	a more conventional a/a	or any
       other divider


       you need	to configure that divider to have your mails properly tagged:

	  maildir_separator = /

       This filter adds	tags to	a message if the named header matches the reg-
       ular  expression	 given.	  The  tags can	be set,	or based on the	match.
       The settings you	can use	are:

       o header	= <header_name>

       o pattern = <regex_pattern>

       o tags =	<tag_list>

       If you surround a tag with {} then it will be replaced with  the	 named

       Some examples are:

	  header = X-Spam-Flag
	  pattern = YES
	  tags = +spam

	  header = List-Id
	  pattern = <(?P<list_id>.*)>
	  tags = +lists;+{list_id}

	  header = X-Redmine-Project
	  pattern = (?P<project>.*)
	  tags = +redmine;+{project}

       SpamFilter and ListMailsFilter are implemented using HeaderMatchingFil-
       ter, and	are only slightly more complicated than	the above examples.

       This removes the	new tag, and adds the inbox tag, to any	 message  that
       isnat  killed  or  spam.	 (The new tags are set in your notmuch config,
       and default to just new.)

       If the new message has been added to a thread  that  has	 already  been
       tagged killed then add the killed tag to	this message.  This allows for
       ignoring	all replies to a particular thread.

       This filter looks for the List-Id header, and if	it finds  it,  adds  a
       tag lists and a tag named lists/<list-id>.

       Add  filter  tagging  mail sent directly	to any of addresses defined in
       Notmuch config file: primary_email  or  other_email.   Default  tag  is
       to-me and can be	customized with	me_tag option.

       The settings you	can use	are:

       o sent_tag = <tag>

	  o Add	 <tag>	to all mails sent from one of your configured mail ad-
	    dresses, and not to	any of your addresses.

	  o The	default	is to add no tag, so you need to specify something.

	  o You	may e.g. use it	to tag all mails sent by you as	 asenta.  This
	    may	 make  special sense in	conjunction with a mail	client that is
	    able to not	only search for	threads	but individual mails as	well.

       o to_transforms = <transformation rules>

	  o Transform To/Cc/Bcc	e-mail addresses  to  tags  according  to  the
	    specified  rules. <transformation rules> is	a space	separated list
	    consisting of _auser_part@domain_part:tagsa style pairs. The	 colon
	    separates  the e-mail address to be	transformed from tags it is to
	    be transformed into. a:tagsa is optional and if empty, auser_parta
	    is	used  as  tag.	atagsa can be a	single tag or semi-colon sepa-
	    rated list of tags.

	  o It can be used for example to easily tag  posts  sent  to  mailing
	    lists which	at this	stage donat have List-Id field.

       The settings you	can use	are:

       o spam_tag = <tag>

	  o Add	<tag> to all mails recognized as spam.

	  o The	default	is aspama.

	  o You	 may  use  it  to  tag your spam as ajunka, ascuma or whatever
	    suits your mood.  Note that	only a single tag is supported here.

       Email will be considered	spam if	the header X-Spam-Flag is present.

   Customizing filters
       To customize these filters, there are basically two different possibil-

       Letas say you like the SpamFilter, but it is way	too polite

       1. Create an filter object and customize	it

	  [SpamFilter.0] # note	the index
	  message = meh

       The  index  is required if you want to create a new SpamFilter in addi-
       tion to the default one.	If you need just  one  customized  SpamFilter,
       you can drop the	index and customize the	default	instance.

       2. Create a new typea|

	  message = I hatez teh	spam!

       and create an object or two

	  message = Me hatez it	too.

       You can provide your own	filter implementations too. You	have to	regis-
       ter your	filters	via entry points. See the afew	 for  examples
       on  how to register your	filters. To add	your filters, you just need to
       install your package in the context of the afew application.

   Configuration Section
       Here is a full sample configuration for move mode:

	  folders = INBOX Junk
	  rename = False
	  max_age = 15

	  # rules
	  INBOX	= 'tag:spam':Junk 'NOT tag:inbox':Archive
	  Junk = 'NOT tag:spam AND tag:inbox':INBOX 'NOT tag:spam':Archive

       Below we	explain	what each bit of this means.

       First you need to specify which folders should  be  checked  for	 mails
       that  are  to  be  moved	(as a whitespace separated list). Folder names
       containing whitespace need to be	quoted:

	  folders = INBOX Junk "Sent Mail"

       Then you	have to	specify	rules that define move actions of the form

	  <src>	= ['<qry>':<dst>]+

       Every mail in the _src_ folder that matches a _qry_ will	be moved  into
       the  _dst_  folder  associated with that	query.	A message that matches
       multiple	queries	will be	copied to multiple destinations.

       You can bind as many rules to a maildir folder as you  deem  necessary.
       Just add	them as	elements of a (whitespace separated) list.

       Please note, though, that you need to specify at	least one rule for ev-
       ery folder given	by the folders option and at least one folder to check
       in order	to use the move	mode.

	  INBOX	= 'tag:spam':Junk

       will  bind  one	rule  to the maildir folder INBOX that states that all
       mails in	said folder that carry (potentially among others) the tag spam
       are to be moved into the	folder Junk.

       With _qry_ being	an arbitrary notmuch query, you	have the power to con-
       struct arbitrarily flexible rules. You can check	 for  the  absence  of
       tags and	look out for combinations of attributes:

	  Junk = 'NOT tag:spam AND tag:inbox':INBOX 'NOT tag:spam':Archive

       The  above  rules  will move all	mails in Junk that donat have the spam
       tag but do have an inbox	tag into the directory INBOX. All other	 mails
       not tagged with spam will be moved into Archive.

   Max Age
       You  can	limit the age of mails you want	to move	by setting the max_age
       option in the configuration section. By providing

	  max_age = 15

       afew will only check mails at most 15 days old.

       Set this	option if you are using	the mbsync IMAP	syncing	tool.	mbsync
       adds  a	unique	identifier to filesa names when	it syncs them.	If the
       rename option is	not set, moving	files can cause	UID conflicts and pre-
       vent  mbsync  from  syncing with	error messages such as aMaildir	error:
       duplicate UID 1234a or aUID 567 is beyond highest assigned UID 89a.

       When the	option is set, afew will rename	files while moving  them,  re-
       moving  the  UID	 but preserving	other mbsync information.  This	allows
       mbsync to assign	a new UID to the file and avoid	UID conflicts.

       If you are using	offlineimap, you can safely ignore this	option.

	  rename = True

       (1) Rules donat manipulate tags.

	  INBOX	= 'NOT tag:inbox':Archive
	  Junk = 'NOT tag:spam':INBOX

       The above combination of	rules might prove tricky, since	you might  ex-
       pect  de-spammed	mails to end up	in INBOX. But since the	Junk rule will
       not add an inbox	tag, the next run in move mode might  very  well  move
       the matching mails into Archive.

       Then again, if you remove the spam tag and do not set an	inbox tag, how
       would you come to expect	the mail would end up in your INBOX folder af-
       ter moving it? ;)

       (2) There is no 1:1 mapping between folders and tags. And thatas	a fea-
       ture. If	you tag	a mail with two	tags and there is a rule for  each  of
       them,  both rules will apply.  Your mail	will be	copied into two	desti-
       nation folders, then removed from its original location.

       You can put python files	in ~/.config/afew/ and they will  be  imported
       by  afew.  If you use that python file to define	a Filter class and use
       the register_filter decorator then you can refer	to it in  your	filter

       So an example small filter you could add	might be:

	  from afew.filters.BaseFilter	import Filter
	  from afew.FilterRegistry import register_filter

	      'fabric':	'deployment',
	      'oldname': 'new-name',

	  class	RedmineFilter(Filter):
	      message =	'Create	tag based on redmine project'
	      query = 'NOT tag:redmine'

	      def handle_message(self, message):
		  project = message.get_header('X-Redmine-Project')
		  if project in	PROJECT_MAPPING:
		      project =	PROJECT_MAPPING[project]
		  self.add_tags(message, 'redmine', project)

       We  have	defined	the message and	query class variables that are used by
       the parent class	Filter.	 The message is	printed	when running with ver-
       bose flags.  The	query is used to select	messages to run	against	- here
       we ensure we donat bother looking at messages weave already looked at.

       The handle_message() method is the key one to implement.	 This will  be
       called  for  each  message  that	 matches the query.  The argument is a
       notmuch message object and the key methods used by the afew filters are
       get_header(), get_filename() and	get_thread().

       Of  the	methods	 inherited  from  the  Filter  class  the key ones are
       add_tags() and remove_tags(), but read about the	implementation or just
       read the	source code to get your	own ideas.

       Once youave defined your	filter,	you can	add it to your config like any
       other filter:


   Database Manager
       The design of the database manager was inspired by alots	database  man-
       ager alot.db.DBManager.

       class afew.Database.Database
	      Convenience wrapper around notmuch.

	      add_message(path,	    sync_maildir_flags=False,	 new_mail_han-
		     Adds the given message to the notmuch index.


			    o path (str) a path	to the message

			    o sync_maildir_flags (bool)	a if True notmuch con-
			      verts the	standard maildir flags to tags

			    o new_mail_handler (a function that	is called with
			      a	notmuch.Message	object as its only argument) a
			      callback for new messages

		     Raises notmuch.NotmuchError if adding the message fails

			    a notmuch.Message object

		     Closes the	notmuch	database if it has been	opened.

		     Executes a	notmuch	query.

			    query (str)	a the query to execute

			    the	query result

		     Return type

	      get_messages(query, full_thread=False)
		     Get all messages mathing the given	query.


			    o query   (str)  a	the  query  to	execute	 using

			    o full_thread (bool) a return  all	messages  from
			      mathing threads

			    an iterator	over notmuch.Message objects

		     Remove the	given message from the notmuch index.

			    path (str) a path to the message

		     Returns all replies to the	given message.

			    message  (notmuch.Message)	a the message to start

			    an iterator	over notmuch.Message objects

		     Returns all messages in the given thread.

			    thread (notmuch.Thread) a the tread	you are	inter-
			    ested in

			    an iterator	over notmuch.Message objects

       class afew.filters.BaseFilter.Filter(database, **kwargs)

		     (Re)Initializes  the  data	 structures  that hold the en-
		     queued changes to the notmuch database.

   Configuration management
   Miscellanious utility functions
       o genindex

       o modindex

       o search

       Justus Winter

       afewmail	project

3.0.0				 Aug 30, 2020			       AFEW(1)


Want to link to this manual page? Use this URL:

home | help