;;; mu4e-config.el --- mu4e email configuration -*- lexical-binding: t; -*- ;; mu4e should already be loaded from .emacs before this file is loaded ;; If not loaded, try to load it ;; Note: mu4e path is symlinked from Homebrew Cellar (add-to-list 'load-path "/opt/homebrew/share/emacs/site-lisp/mu4e") ;; Ensure Emacs can find the mu binary (Homebrew on Apple Silicon) (setq mu4e-mu-binary "/opt/homebrew/bin/mu") (require 'mu4e) ;; HTML rendering configuration for mu4e 1.12+ ;; Use shr as the default renderer (setq mm-text-html-renderer 'shr) ;; Configure shr for better plain text display ;; (setq shr-use-colors nil) ; No colors ;; (setq shr-use-fonts nil) ; No variable fonts ;; (setq shr-width 80) ; 80 column width (setq shr-bullet "• ") ; Nice bullet ;; Increase font size in SHR (HTML rendering) (defun my-shr-rescale-font () "Increase font size in SHR rendered content." (text-scale-set 1)) ; Increase by 1 step, adjust as needed (2, 3, etc.) ;; Apply font scaling to mu4e HTML viewing (add-hook 'mu4e-view-mode-hook 'my-shr-rescale-font) ;; Create a custom command to view HTML with pandoc (defun mu4e-view-html-with-pandoc () "View the HTML part of the current message using pandoc." (interactive) (let ((msg (mu4e-message-at-point))) (unless msg (mu4e-error "No message at point")) (let* ((path (mu4e-message-field msg :path)) (pandoc-buffer "*mu4e-pandoc*") (temp-dir (make-temp-file "mu4e-extract" t))) (if path (progn (with-current-buffer (get-buffer-create pandoc-buffer) (erase-buffer) (insert "=== HTML Email Rendered with Pandoc ===\n\n") ;; Extract all parts (call-process "mu" nil nil nil "extract" "--save-all" (format "--target-dir=%s" temp-dir) path) ;; Find HTML files and convert them (let ((html-files (directory-files temp-dir t "\\.html?$"))) (if html-files (dolist (html-file html-files) (insert (shell-command-to-string (format "pandoc -f html -t markdown --wrap=auto --columns=80 '%s' 2>/dev/null" html-file)))) ;; No HTML files found, try extracting from message directly (let ((raw-msg (shell-command-to-string (format "cat '%s'" path)))) ;; Look for HTML content between boundaries (if (string-match "Content-Type: text/html" raw-msg) (progn (insert "Converting HTML content...\n\n") (let ((temp-html (make-temp-file "mu4e-msg" nil ".html"))) (with-temp-file temp-html (insert raw-msg)) (insert (shell-command-to-string (format "cat '%s' | sed -n '/Content-Type: text\\/html/,/^--/p' | sed '1,/^$/d' | sed '/^--/,$d' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null" temp-html))) (delete-file temp-html))) (insert "No HTML content found in this message.\n"))))) ;; Clean up temp directory (delete-directory temp-dir t) (goto-char (point-min)) (view-mode) (display-buffer (current-buffer)))) (mu4e-warn "Cannot access message file"))))) ;; Simpler approach: Add action to view HTML with pandoc (add-to-list 'mu4e-view-actions '("pandoc" . (lambda (msg) (let* ((path (mu4e-message-field msg :path)) (pandoc-buffer "*mu4e-pandoc*")) (when path (with-current-buffer (get-buffer-create pandoc-buffer) (erase-buffer) (insert "=== HTML Email Rendered with Pandoc ===\n\n") (let ((html-content (shell-command-to-string (format "mu view '%s' 2>/dev/null | awk '/text\\/html/{flag=1; next} flag && /^--/{exit} flag' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null" path)))) (if (and html-content (> (length html-content) 0)) (insert html-content) ;; If no HTML part found, show message (insert "No HTML content found in this message.\n\n") (insert (shell-command-to-string (format "mu view '%s'" path))))) (goto-char (point-min)) (view-mode) (display-buffer (current-buffer))))))) t) ;; Add keybinding for pandoc view (with-eval-after-load 'mu4e-view (define-key mu4e-view-mode-map (kbd "H") 'mu4e-view-html-with-pandoc)) (message "mu4e: HTML rendering configured. Press 'H' in message view to render with pandoc.") ;; Basic mu4e settings (setq mu4e-maildir "~/Maildir" ;; Use our processing script instead of plain mbsync ;; This will sync mail and fix List-Id headers mu4e-get-mail-command (expand-file-name "process-mail.sh" user-emacs-directory) mu4e-update-interval 300 ; Update every 5 minutes mu4e-compose-signature-auto-include nil mu4e-view-show-images t mu4e-view-show-addresses t mu4e-change-filenames-when-moving t ; Needed for mbsync mu4e-index-cleanup t ; Clean up after moving mu4e-index-lazy-check nil ; Don't be lazy about indexing mu4e-hide-index-messages t) ; Hide indexing messages to avoid errors ;; Function to get current context's maildir prefix (defun mu4e-current-context-maildir-prefix () "Get the maildir prefix for the current context." (if mu4e-context-current (let ((context-name (mu4e-context-name mu4e-context-current))) (format "maildir:/%s/*" context-name)) "")) ;; Bookmarks (shortcuts to common searches) ;; Use setq to define the complete list at once (setq mu4e-bookmarks '(;; Basic views - work in current context (:name "Unread messages" :query "flag:unread AND NOT flag:trashed" :key ?u) (:name "Today's messages" :query "date:today..now" :key ?t) (:name "Last 7 days" :query "date:7d..now" :key ?w) ;; Smart mailboxes - search across all contexts (:name "📌 Important" :query "(flag:flagged OR prio:high OR from:/boss|manager|ceo|director|important/ OR subject:/urgent|important|critical|asap/) AND NOT flag:trashed" :key ?i) (:name "📰 Newsletters" :query "(from:/nytimes|newyorktimes|atlantic|politico/ OR from:nytimes.com OR from:theatlantic.com OR from:politico.com OR from:politico.eu) AND NOT flag:trashed AND NOT flag:flagged" :key ?n) (:name "🛍️ Purchases & Orders" :query "(from:/amazon|ebay|paypal|stripe|shopify|order|store|shop|invoice|receipt/ OR subject:/order|invoice|receipt|purchase|payment|confirmation|shipping|delivery|tracking/) AND NOT flag:trashed" :key ?p) (:name "📎 Attachments" :query "flag:attach AND NOT flag:trashed" :key ?a) (:name "🎫 Travel & Tickets" :query "(from:/airline|hotel|booking|expedia|airbnb|uber|lyft|train|eventbrite|ticketmaster/ OR subject:/booking|reservation|ticket|flight|itinerary|confirmation/) AND NOT flag:trashed" :key ?v) (:name "💰 Finance & Banking" :query "(from:/bank|credit|visa|mastercard|amex|insurance|tax|accountant/ OR subject:/statement|balance|transaction|payment/) AND NOT flag:trashed" :key ?f) (:name "👥 Social & Forums" :query "(from:/facebook|twitter|linkedin|instagram|reddit|github|gitlab|discourse|forum/ OR subject:/commented|replied|mentioned|tagged|followed/) AND NOT flag:trashed" :key ?s) ;; Mailing Lists (Personal context) (:name "📋 All Mailing Lists" :query "maildir:/Personal/Lists AND NOT flag:trashed" :key ?L) (:name "📋 C++ std-discussion" :query "list:std-discussion.lists.isocpp.org AND NOT flag:trashed" :key ?C) (:name "📋 Qt Interest" :query "list:interest.qt-project.org AND NOT flag:trashed" :key ?q) (:name "📋 Boost" :query "list:boost.lists.boost.org AND NOT flag:trashed" :key ?b) (:name "📋 GCC" :query "list:gcc.gcc.gnu.org AND NOT flag:trashed" :key ?G))) (setq mu4e-maildir-shortcuts '(("/Personal/INBOX" . ?i) ("/Personal/Sent" . ?s) ("/Personal/Trash" . ?t) ("/Personal/Drafts" . ?d) ("/Personal/Spam " . ?S) ("/Personal/Archive" . ?a) ("/Personal/Lists" . ?l))) ;; Custom function for fuzzy relative timestamps (defun my-mu4e-format-date (date) "Format DATE as a fuzzy relative time string." (let* ((now (float-time)) (time (float-time date)) (diff (- now time)) (sec diff) (min (/ diff 60)) (hour (/ diff 3600)) (day (/ diff 86400)) (week (/ diff 604800)) (month (/ diff 2592000)) (year (/ diff 31536000))) (cond ((< sec 60) "just now") ((< min 2) "1 min ago") ((< min 60) (format "%d mins ago" (truncate min))) ((< hour 2) "1 hour ago") ((< hour 24) (format "%d hours ago" (truncate hour))) ((< day 2) "yesterday") ((< day 7) (format "%d days ago" (truncate day))) ((< week 2) "1 week ago") ((< week 4) (format "%d weeks ago" (truncate week))) ((< month 2) "1 month ago") ((< month 12) (format "%d months ago" (truncate month))) ((< year 2) "1 year ago") (t (format "%d years ago" (truncate year)))))) ;; Custom header field for fuzzy date (add-to-list 'mu4e-header-info-custom '(:fuzzy-date . (:name "Date" :shortname "Date" :function (lambda (msg) (my-mu4e-format-date (mu4e-message-field msg :date)))))) ;; UI Configuration - use fuzzy dates in headers (setq mu4e-headers-fields '((:fuzzy-date . 15) ; Fuzzy date with 15 char width (:flags . 6) (:from-or-to . 22) (:subject))) ;; Make mu4e respect the current color theme (setq mu4e-view-use-gnus t) ; Use Gnus article mode for better theme support (setq shr-use-colors nil) ; Let the theme handle colors in HTML emails ;; Use full window for reading emails (setq mu4e-split-view nil) ; Don't split, use full window for message view ;; Make headers/search view respect theme (setq mu4e-headers-unread-face 'bold) ; Use theme's bold face instead of custom color (setq mu4e-headers-highlight-face 'highlight) ; Use theme's highlight face (setq mu4e-headers-flagged-face 'font-lock-warning-face) ; Use theme's warning face ;; Enable inline images (setq mu4e-view-show-images t) (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) ;; Colorize inline patches in emails (require 'diff-mode) (defun mu4e-colorize-patch () "Colorize patches in mu4e view buffers." (save-excursion (goto-char (point-min)) ;; Look for patch sections (starting with diff, ---, or @@) (while (re-search-forward "^\\(diff \\|--- \\|\\+\\+\\+ \\|@@ \\)" nil t) (let ((patch-start (match-beginning 0))) ;; Find the end of the patch (if (re-search-forward "^[^-+@ \t]" nil t) (backward-char) (goto-char (point-max))) (let ((patch-end (point)) (inhibit-read-only t)) ;; Apply diff-mode font-lock to the patch region (add-text-properties patch-start patch-end '(face nil)) ; Reset face first (save-restriction (narrow-to-region patch-start patch-end) (diff-mode) (font-lock-fontify-region patch-start patch-end) (widen)))))) ;; Also colorize simple diff lines (save-excursion (goto-char (point-min)) (while (re-search-forward "^\\(-.*\\)$" nil t) (let ((inhibit-read-only t)) (add-face-text-property (match-beginning 1) (match-end 1) 'diff-removed))) (goto-char (point-min)) (while (re-search-forward "^\\(\\+.*\\)$" nil t) (let ((inhibit-read-only t)) (add-face-text-property (match-beginning 1) (match-end 1) 'diff-added))) (goto-char (point-min)) (while (re-search-forward "^\\(@@.*@@\\).*$" nil t) (let ((inhibit-read-only t)) (add-face-text-property (match-beginning 1) (match-end 1) 'diff-hunk-header))))) ;; Hook to colorize patches when viewing messages (add-hook 'mu4e-view-mode-hook 'mu4e-colorize-patch) ;; For Gnus article mode (when mu4e-view-use-gnus is t) (with-eval-after-load 'gnus-art (add-hook 'gnus-article-mode-hook 'mu4e-colorize-patch)) ;; Use mu4e for composing email (setq mail-user-agent 'mu4e-user-agent) ;; Keybindings (global-set-key (kbd "C-x m") 'mu4e) (global-set-key (kbd "C-x M") 'mu4e-compose-new) ;; Transient menu (mu4e 1.12.9+) (with-eval-after-load 'mu4e (require 'mu4e-transient nil t) (when (featurep 'mu4e-transient) (global-set-key (kbd "C-c m") #'mu4e-transient-menu))) ;; Double-check pandoc is available and being used (if (executable-find "pandoc") (message "Pandoc found at: %s" (executable-find "pandoc")) (message "WARNING: Pandoc not found!")) ;; Prefer plain text when available, but show HTML when it's the only option (setq mu4e-view-prefer-html nil) ;; Actions (add-to-list 'mu4e-view-actions '("ViewInBrowser" . mu4e-action-view-in-browser) t) ;; Don't keep message buffers around (setq message-kill-buffer-on-exit t) ;; For Proton Bridge: Comment out custom marks for now to avoid errors ;; The default behavior will be: ;; d - move to trash ;; D - delete permanently ;; r - refile/archive ;; If you want to customize deletion behavior, uncomment and adjust: ;; (with-eval-after-load 'mu4e ;; ;; Make 'd' archive instead of trash ;; (setf (alist-get 'trash mu4e-marks) ;; (list :char '("d" . "▼") ;; :prompt "archive" ;; :dyn-target (lambda (target msg) ;; (mu4e-get-refile-folder msg)) ;; :action (lambda (docid msg target) ;; (mu4e--server-move docid ;; (mu4e--mark-check-target target) ;; "-N"))))) ;; Test function to verify pandoc is working (defun mu4e-test-pandoc () "Test if pandoc is being used for HTML rendering." (interactive) (let ((test-html "
This is a test paragraph with emphasis.