1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
;;; 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