;;; pkg-email.el --- Email client configuration -*- lexical-binding: t -*- ;; Copyright (C) 2026 Cytrogen ;;; Commentary: ;; Email configuration using mu4e. ;; Account configuration is stored in ~/.emacs.d/email-accounts.el ;;; Code: ;;; ============================================================ ;;; Account Configuration (不依赖 mu4e) ;;; ============================================================ (defvar my/email-accounts nil "Email account configurations loaded from external file.") (defvar my/email-accounts-file (expand-file-name "email-accounts.el" user-emacs-directory) "Path to the email accounts configuration file.") (defun my/email--load-accounts () "Load email accounts from external config file." (when (file-exists-p my/email-accounts-file) (load-file my/email-accounts-file))) ;; 立即加载账号(不依赖 mu4e) (my/email--load-accounts) ;;; ============================================================ ;;; mu4e Setup (延迟加载) ;;; ============================================================ ;; 添加 mu4e 路径并加载 autoloads (let ((mu4e-path "/usr/share/emacs/site-lisp/mu4e")) (when (file-directory-p mu4e-path) (add-to-list 'load-path mu4e-path) (load "mu4e-autoloads" t t))) (with-eval-after-load 'mu4e ;; Context 生成函数 (defun my/email--make-context (account) "Create a mu4e-context from ACCOUNT plist." (let* ((name (plist-get account :name)) (email (plist-get account :email)) (fullname (plist-get account :fullname)) (maildir (plist-get account :maildir)) (inbox (plist-get account :inbox)) (sent (plist-get account :sent)) (drafts (plist-get account :drafts)) (trash (plist-get account :trash)) (archive (plist-get account :archive)) (maildir-prefix (concat "/" maildir))) (make-mu4e-context :name name :enter-func (lambda () (mu4e-message "Switched to %s" name)) :leave-func (lambda () (mu4e-message "Left %s" name)) :match-func (lambda (msg) (when msg (string-prefix-p maildir-prefix (mu4e-message-field msg :maildir)))) :vars (list (cons 'user-mail-address email) (cons 'user-full-name fullname) (cons 'mu4e-sent-folder (concat maildir-prefix "/" sent)) (cons 'mu4e-drafts-folder (concat maildir-prefix "/" drafts)) (cons 'mu4e-trash-folder (concat maildir-prefix "/" trash)) (cons 'mu4e-refile-folder (concat maildir-prefix "/" archive)) (cons 'mu4e-maildir-shortcuts (list (list :maildir (concat maildir-prefix "/" inbox) :key ?i :name (concat maildir-prefix "/" inbox)) (list :maildir (concat maildir-prefix "/" sent) :key ?s :name (concat maildir-prefix "/" sent)) (list :maildir (concat maildir-prefix "/" drafts) :key ?d :name (concat maildir-prefix "/" drafts)) (list :maildir (concat maildir-prefix "/" trash) :key ?t :name (concat maildir-prefix "/" trash)) (list :maildir (concat maildir-prefix "/" archive) :key ?a :name (concat maildir-prefix "/" archive)))))))) (defun my/email--setup-contexts () "Generate mu4e-contexts from `my/email-accounts'." (when my/email-accounts (setq mu4e-contexts (mapcar #'my/email--make-context my/email-accounts)) (setq mu4e-context-policy 'pick-first) (setq mu4e-compose-context-policy 'ask-if-none))) ;; mu4e 基础配置 (setq mu4e-maildir "~/Mail") (setq mu4e-get-mail-command "mbsync -a") (setq mu4e-update-interval 300) (setq message-kill-buffer-on-exit t) (setq mu4e-headers-auto-update t) (setq mu4e-compose-signature-auto-include nil) (setq mu4e-view-show-images t) (setq mu4e-view-show-addresses t) (setq mu4e-use-fancy-chars t) (setq mu4e-compose-dont-reply-to-self t) (setq mu4e-modeline-support t) ;; HTML 渲染配置 (setq mm-text-html-renderer 'shr) (setq shr-use-colors t) (setq shr-use-fonts nil) (setq shr-max-image-proportion 0.6) (setq shr-image-animate nil) (setq shr-width nil) (setq shr-discard-aria-hidden t) (setq shr-cookie-policy nil) ;; Gnus 图片配置 (setq gnus-inhibit-images nil) (setq gnus-blocked-images ".") ; 阻止追踪图片 ;; 自动换行 (add-hook 'mu4e-view-mode-hook #'visual-line-mode) (setq mm-fill-flowed t) ;; msmtp 发送配置 (setq sendmail-program "/usr/bin/msmtp") (setq send-mail-function 'message-send-mail-with-sendmail) (setq message-send-mail-function 'message-send-mail-with-sendmail) (setq message-sendmail-f-is-evil t) (setq message-sendmail-extra-arguments '("--read-envelope-from")) ;; 动态标记为垃圾邮件(支持所有账户) (defun my/mu4e-mark-as-spam () "Mark message at point as spam (move to Spam folder of current account)." (interactive) (let* ((ctx (mu4e-context-current)) (ctx-name (when ctx (mu4e-context-name ctx))) (spam-folder (cond ((string= ctx-name "Gmail") "/Gmail/[Gmail]/Spam") ((string= ctx-name "iCloud") "/iCloud/Junk") ((string= ctx-name "Boo") "/Cytrogen/Junk Mail") (t "/Gmail/[Gmail]/Spam")))) ; 默认 (mu4e-mark-set 'move spam-folder))) (define-key mu4e-headers-mode-map (kbd "C-d") #'my/mu4e-mark-as-spam) (define-key mu4e-view-mode-map (kbd "C-d") #'my/mu4e-mark-as-spam) ;; 垃圾邮件过滤查询 (defvar my/spam-filter-query (concat "NOT flag:trashed " ; 排除已删除 "NOT maildir:/Gmail/\\[Gmail\\]/Spam " ; 排除垃圾文件夹 "NOT from:noreply " ; 排除无回复地址 "NOT from:no-reply " "NOT subject:unsubscribe " ; 排除退订相关 "NOT subject:\"verify your\" " ; 排除验证邮件 "NOT subject:\"confirm your\" " "NOT subject:\"your order\" ") ; 可根据需要扩展 "Query to filter out spam-like messages.") ;; 添加书签:过滤后的 inbox 视图 (add-to-list 'mu4e-bookmarks '(:name "Clean Inbox" :query (lambda () (concat "maildir:/Gmail/Inbox " my/spam-filter-query)) :key ?c)) (add-to-list 'mu4e-bookmarks '(:name "All Inboxes (Clean)" :query (lambda () (concat "(maildir:/Gmail/Inbox OR " "maildir:/iCloud/Inbox OR " "maildir:/Cytrogen/Inbox) " my/spam-filter-query)) :key ?C)) ;; 设置 contexts (my/email--setup-contexts) ;; 修复 context 切换时 maildir shortcuts 不更新的问题 (add-hook 'mu4e-context-changed-hook (lambda () (setq mu4e--maildir-items-cached nil))) ;; 链接点击支持 (define-key mu4e-view-mode-map (kbd "RET") #'mu4e-view-browse-url-from-binding)) ;;; ============================================================ ;;; Interactive Commands ;;; ============================================================ (defun my/email-reload-accounts () "Reload email accounts from config file." (interactive) (my/email--load-accounts) (when (featurep 'mu4e) (my/email--setup-contexts)) (message "Loaded %d email account(s)" (length my/email-accounts))) (provide 'pkg-email) ;;; pkg-email.el ends here