-
-
Save wakatara/d8fd67db613f47e5014a9c086ef04344 to your computer and use it in GitHub Desktop.
Notmuch configuration for Hey.com Style workflows.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Specific Notmuch filters (and saved-searches) for: | |
+ The Feed (newsletters, blogs) | |
+ The Paper trail (receipts, ledger) | |
+ Screened Inbox (mail from folks you actually want to read) | |
+ Previously Seen (important mail that you've already read) | |
+ Unscreened Inbox (potential spam / stuff you don't want) | |
- Elisp Functions to move / categorize emails from a particular sender. | |
+ Adds tags needed by filters defined above to all email sent by a particular sender | |
+ Creates an entry in a DB file, which is used by the Notmuch post-new script when indexing new email, to auto-add the relevant tags. | |
- Shell script (the Notmuch post-new hook) to categorize emails when indexing them. | |
- Demo of this functionality: https://www.youtube.com/watch?v=wuSPssykPtE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; init-notmuch.el --- configuration for using notmuch to manage email | |
;;; Author: Vedang Manerikar | |
;;; Created on: 07th June 2014 | |
;;; Copyright (c) 2014 Vedang Manerikar <[email protected]> | |
;; This file is not part of GNU Emacs. | |
;;; License: | |
;; This program is free software; you can redistribute it and/or | |
;; modify it under the terms of the Do What The Fuck You Want to | |
;; Public License, Version 2, which is included with this distribution. | |
;; See the file LICENSE.txt | |
;;; Commentary: | |
;; Put this file somewhere on your load-path, after notmuch is loaded. | |
;; Eg: | |
;; (require 'notmuch) | |
;; (require 'init-notmuch) | |
;; The variable `notmuch-mail-dir' needs to be defined (for example, | |
;; in your personal.el file) | |
;;; Code: | |
(setq user-mail-address (notmuch-user-primary-email) | |
user-full-name (notmuch-user-name) | |
message-send-mail-function 'message-send-mail-with-sendmail | |
;; we substitute sendmail with msmtp | |
sendmail-program (executable-find "msmtp") | |
message-sendmail-envelope-from 'header | |
mail-specify-envelope-from t | |
notmuch-archive-tags '("-inbox" "-unread" "+archived") | |
notmuch-show-mark-read-tags '("-inbox" "-unread" "+archived") | |
notmuch-search-oldest-first nil | |
notmuch-show-indent-content nil | |
notmuch-hooks-dir (expand-file-name ".notmuch/hooks" notmuch-mail-dir)) | |
;;; My Notmuch start screen: | |
(progn | |
(setq notmuch-saved-searches nil) | |
(push '(:name "Inbox" | |
:query "tag:inbox AND tag:screened AND tag:unread" | |
:key "i" | |
:search-type 'tree) | |
notmuch-saved-searches) | |
(push '(:name "Previously Seen" | |
:query "tag:screened AND NOT tag:unread" | |
:key "I") | |
notmuch-saved-searches) | |
(push '(:name "Unscreened" | |
:query "tag:inbox AND NOT tag:screened" | |
:key "s") | |
notmuch-saved-searches) | |
(push '(:name "The Feed" | |
:query "tag:thefeed" | |
:key "f" | |
:search-type 'tree) | |
notmuch-saved-searches) | |
(push '(:name "The Papertrail" | |
:query "tag:/ledger/" | |
:key "p") | |
notmuch-saved-searches)) | |
;; Integrate with org-mode | |
(require 'ol-notmuch) | |
(eval-after-load 'notmuch-show | |
'(progn | |
;; Bindings in `notmuch-show-mode' | |
(define-key notmuch-show-mode-map (kbd "r") | |
'notmuch-show-reply) | |
(define-key notmuch-show-mode-map (kbd "R") | |
'notmuch-show-reply-sender) | |
(define-key notmuch-show-mode-map (kbd "C") | |
'vedang/notmuch-reply-later) | |
;; Bindings in `notmuch-search-mode' | |
(define-key notmuch-search-mode-map (kbd "r") | |
'notmuch-search-reply-to-thread) | |
(define-key notmuch-search-mode-map (kbd "R") | |
'notmuch-search-reply-to-thread-sender) | |
(define-key notmuch-search-mode-map (kbd "/") | |
'notmuch-search-filter) | |
(define-key notmuch-search-mode-map (kbd "A") | |
'vedang/notmuch-archive-all) | |
(define-key notmuch-search-mode-map (kbd "D") | |
'vedang/notmuch-delete-all) | |
(define-key notmuch-search-mode-map (kbd "L") | |
'vedang/notmuch-filter-by-from) | |
(define-key notmuch-search-mode-map (kbd ";") | |
'vedang/notmuch-search-by-from) | |
(define-key notmuch-search-mode-map (kbd "d") | |
'vedang/notmuch-search-delete-and-archive-thread) | |
;; The HEY Workflow Bindings | |
(define-key notmuch-search-mode-map (kbd "S") | |
'vedang/notmuch-move-sender-to-spam) | |
(define-key notmuch-search-mode-map (kbd "I") | |
'vedang/notmuch-move-sender-to-screened) | |
(define-key notmuch-search-mode-map (kbd "P") | |
'vedang/notmuch-move-sender-to-papertrail) | |
(define-key notmuch-search-mode-map (kbd "f") | |
'vedang/notmuch-move-sender-to-thefeed) | |
(define-key notmuch-search-mode-map (kbd "C") | |
'vedang/notmuch-reply-later) | |
;; Bindings in `notmuch-tree-mode' | |
(define-key notmuch-tree-mode-map (kbd "C") | |
'vedang/notmuch-reply-later))) | |
(defun vedang/notmuch-archive-all () | |
"Archive all the emails in the current view." | |
(interactive) | |
(notmuch-search-archive-thread nil (point-min) (point-max))) | |
(defun vedang/notmuch-delete-all () | |
"Archive all the emails in the current view. | |
Mark them for deletion by cron job." | |
(interactive) | |
(notmuch-search-tag-all '("+deleted")) | |
(vedang/notmuch-archive-all)) | |
(defun vedang/notmuch-search-delete-and-archive-thread () | |
"Archive the currently selected thread. Add the deleted tag as well." | |
(interactive) | |
(notmuch-search-add-tag '("+deleted")) | |
(notmuch-search-archive-thread)) | |
(defun vedang/notmuch-tag-and-archive (tag-changes &optional beg end) | |
"Prompt the user for TAG-CHANGES. | |
Apply the TAG-CHANGES to region and also archive all the emails. | |
When called directly, BEG and END provide the region." | |
(interactive (notmuch-search-interactive-tag-changes)) | |
(notmuch-search-tag tag-changes beg end) | |
(notmuch-search-archive-thread nil beg end)) | |
(defun vedang/notmuch-search-find-from () | |
"A helper function to find the email address for the given email." | |
(let ((notmuch-addr-sexp (first | |
(notmuch-call-notmuch-sexp "address" | |
"--format=sexp" | |
"--format-version=1" | |
"--output=sender" | |
(notmuch-search-find-thread-id))))) | |
(plist-get notmuch-addr-sexp :address))) | |
(defun vedang/notmuch-filter-by-from () | |
"Filter the current search view to show all emails sent from the sender of the current thread." | |
(interactive) | |
(notmuch-search-filter (concat "from:" (vedang/notmuch-search-find-from)))) | |
(defun vedang/notmuch-search-by-from (&optional no-display) | |
"Show all emails sent from the sender of the current thread. | |
NO-DISPLAY is sent forward to `notmuch-search'." | |
(interactive) | |
(notmuch-search (concat "from:" (vedang/notmuch-search-find-from)) | |
notmuch-search-oldest-first | |
nil | |
nil | |
no-display)) | |
(defun vedang/notmuch-tag-by-from (tag-changes &optional beg end refresh) | |
"Apply TAG-CHANGES to all emails from the sender of the current thread. | |
BEG and END provide the region, but are ignored. They are defined | |
since `notmuch-search-interactive-tag-changes' returns them. If | |
REFRESH is true, refresh the buffer from which we started the | |
search." | |
(interactive (notmuch-search-interactive-tag-changes)) | |
(let ((this-buf (current-buffer))) | |
(vedang/notmuch-search-by-from t) | |
;; This is a dirty hack since I can't find a way to run a | |
;; temporary hook on `notmuch-search' completion. So instead of | |
;; waiting on the search to complete in the background and then | |
;; making tag-changes on it, I will just sleep for a short amount | |
;; of time. This is generally good enough and works, but is not | |
;; guaranteed to work every time. I'm fine with this. | |
(sleep-for 0.5) | |
(notmuch-search-tag-all tag-changes) | |
(when refresh | |
(set-buffer this-buf) | |
(notmuch-refresh-this-buffer)))) | |
(defun vedang/notmuch-add-addr-to-db (nmaddr nmdbfile) | |
"Add the email address NMADDR to the db-file NMDBFILE." | |
(append-to-file (format "%s\n" nmaddr) nil nmdbfile)) | |
(defun vedang/notmuch-move-sender-to-thefeed () | |
"For the email at point, move the sender of that email to the feed. | |
This means: | |
1. All new email should go to the feed and skip the inbox altogether. | |
2. All existing email should be updated with the tag =thefeed=. | |
3. All existing email should be removed from the inbox." | |
(interactive) | |
(vedang/notmuch-add-addr-to-db (vedang/notmuch-search-find-from) | |
(format "%s/thefeed.db" notmuch-hooks-dir)) | |
(vedang/notmuch-tag-by-from '("+thefeed" "+archived" "-inbox"))) | |
(defun vedang/notmuch-move-sender-to-papertrail (tag-name) | |
"For the email at point, move the sender of that email to the papertrail. | |
This means: | |
1. All new email should go to the papertrail and skip the inbox altogether. | |
2. All existing email should be updated with the tag =ledger/TAG-NAME=. | |
3. All existing email should be removed from the inbox." | |
(interactive "sTag Name: ") | |
(vedang/notmuch-add-addr-to-db (format "%s %s" | |
tag-name | |
(vedang/notmuch-search-find-from)) | |
(format "%s/ledger.db" notmuch-hooks-dir)) | |
(let ((tag-string (format "+ledger/%s" tag-name))) | |
(vedang/notmuch-tag-by-from (list tag-string "+archived" "-inbox" "-unread")))) | |
(defun vedang/notmuch-move-sender-to-screened () | |
"For the email at point, move the sender of that email to Screened Emails. | |
This means: | |
1. All new email should be tagged =screened= and show up in the inbox. | |
2. All existing email should be updated to add the tag =screened=." | |
(interactive) | |
(vedang/notmuch-add-addr-to-db (vedang/notmuch-search-find-from) | |
(format "%s/screened.db" notmuch-hooks-dir)) | |
(vedang/notmuch-tag-by-from '("+screened"))) | |
(defun vedang/notmuch-move-sender-to-spam () | |
"For the email at point, move the sender of that email to spam. | |
This means: | |
1. All new email should go to =spam= and skip the inbox altogether. | |
2. All existing email should be updated with the tag =spam=. | |
3. All existing email should be removed from the inbox." | |
(interactive) | |
(vedang/notmuch-add-addr-to-db (vedang/notmuch-search-find-from) | |
(format "%s/spam.db" notmuch-hooks-dir)) | |
(vedang/notmuch-tag-by-from '("+spam" "+deleted" "+archived" "-inbox" "-unread" "-screened"))) | |
(defun vedang/notmuch-reply-later () | |
"Capture this email for replying later." | |
(interactive) | |
;; You need `org-capture' to be set up for this to work. Add this | |
;; code somewhere in your init file after `org-cature' is loaded: | |
;; (push '("r" "Respond to email" | |
;; entry (file org-default-notes-file) | |
;; "* TODO Respond to %:from on %:subject :email: \nSCHEDULED: %t\n%U\n%a\n" | |
;; :clock-in t | |
;; :clock-resume t | |
;; :immediate-finish t) | |
;; org-capture-templates) | |
(org-capture nil "r") | |
;; The rest of this function is just a nice message in the modeline. | |
(let* ((email-subject (format "%s..." | |
(substring (notmuch-show-get-subject) 0 15))) | |
(email-from (format "%s..." | |
(substring (notmuch-show-get-from) 0 15))) | |
(email-string (format "%s (From: %s)" email-subject email-from))) | |
(message "Noted! Reply Later: %s" email-string))) | |
;; Sign messages by default. | |
(add-hook 'message-setup-hook 'mml-secure-sign-pgpmime) | |
(setq notmuch-address-selection-function | |
(lambda (prompt collection initial-input) | |
(completing-read prompt | |
(cons initial-input collection) | |
nil | |
t | |
nil | |
'notmuch-address-history))) | |
(defun disable-auto-fill () | |
"I don't want `auto-fill-mode'." | |
(auto-fill-mode -1)) | |
(add-hook 'message-mode-hook 'disable-auto-fill) | |
(provide 'init-notmuch) | |
;;; init-notmuch.el ends here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Based On: https://gist.githubusercontent.com/frozencemetery/5042526/raw/57195ba748e336de80c27519fe66e428e5003ab8/post-new | |
# Install this by moving this file to <maildir>/.notmuch/hooks/post-new | |
# NOTE: you need to define your maildir in the vardiable nm_maildir (just a few lines below in this script) | |
# Also create empty files for: | |
# 1. thefeed.db (things you want to read every once in a while) | |
# 2. spam.db (things you never want to see) | |
# 3. screened.db (your inbox) | |
# 4. ledger.db (papertrail) | |
# in the hooks folder. | |
# More info about hooks: https://notmuchmail.org/manpages/notmuch-hooks-5/ | |
echo "starting NM post-new script" | |
# NOTE: You will need to define your maildir here! | |
export nm_maildir="$HOME/Documents/mail" | |
export start="-1" | |
function timer_start { | |
echo -n " starting $1" | |
export start=$(date +"%s") | |
} | |
function timer_end { | |
end=$(date +"%s") | |
delta=$(($end-$start)) | |
mins=$(($delta / 60)) | |
secs=$(($delta - ($mins*60))) | |
echo " -- $1 completed: ${mins} minutes, ${secs} seconds" | |
export start="-1" # sanity requires this or similar | |
} | |
timer_start "ledger" | |
while IFS= read -r line; do | |
nm_tag=$(echo "$line" | cut -d' ' -f1 -) | |
nm_entry=$(echo "$line" | cut -d' ' -f2 -) | |
if [ -z "$nm_entry" ]; then | |
/usr/local/bin/notmuch tag +archived +ledger/"$nm_tag" -inbox -- tag:inbox and tag:unread and from:"$nm_entry" | |
fi | |
# echo -n "Handling entry: $nm_tag, $nm_entry" | |
done < $nm_maildir/.notmuch/hooks/ledger.db | |
timer_end "ledger" | |
timer_start "unsubscribable_spam" | |
for entry in $(cat $nm_maildir/.notmuch/hooks/spam.db); do | |
if [ -z "$entry" ]; then | |
/usr/local/bin/notmuch tag +spam +deleted +archived -inbox -unread -- tag:inbox and tag:unread and from:"$entry" | |
fi | |
done | |
timer_end "unsubscribable_spam" | |
timer_start "thefeed" | |
for entry in $(cat $nm_maildir/.notmuch/hooks/thefeed.db); do | |
if [ -z "$entry" ]; then | |
/usr/local/bin/notmuch tag +thefeed +archived -inbox -- tag:inbox and tag:unread and from:"$entry" | |
fi | |
done | |
timer_end "thefeed" | |
timer_start "Screened" | |
for entry in $(cat $nm_maildir/.notmuch/hooks/screened.db); do | |
if [ -z "$entry" ]; then | |
/usr/local/bin/notmuch tag +screened -- tag:inbox and tag:unread and from:"$entry" | |
fi | |
done | |
timer_end "Screened" | |
echo "Completing NM post-new script; goodbye" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment