1 邮件发送

在 Emacs 中发邮件主要通过 message-mode 和相应的 SMTP 配置来实现。message-mode 是 Emacs 内置的邮件撰写模式,提供基本的邮件编辑功能。smtpmail 负责通过 SMTP 协议实际发送邮件。

为安全起见,邮箱口令没有写在配置里,Emacs 会自动使用 ~/.authinfo.gpg 文件里的用户登录信息,文件 ~/.authinfo.gpg 是在 init.el 里配置的。

;; 使用 use-package 配置发邮件
(use-package message
  :ensure nil ; 内置包
  :config
  ;; 设置发件人信息
  (setq user-full-name "XXX"
        user-mail-address "subdudu@example1.com")
)

(use-package smtpmail
  :ensure nil
  :custom
  (smtpmail-stream-type 'starttls)
  (smtpmail-smtp-server "smtp.qq.com")
  (smtpmail-smtp-service 587)
  (message-send-mail-function 'smtpmail-send-it))

或者,我使用了 mu4e:

(use-package mu4e
  :ensure nil ; 因为 mu4e 通常随 mu 系统级安装,不从 ELPA 安装
  :defer t
  :commands (mu4e-update-mail-and-index)
  :custom
  ;; 本地 Maildir 路径
  (mu4e-maildir (expand-file-name "MailDir" (or (getenv "XDG_DATA_HOME")
                                                "~/.local/share/")))
  ;; 设置为选择第一个context,如果设置为其它值,就会被询问
  (mu4e-context-policy 'pick-first)
  ;; 设置发送邮件 (SMTP)
  ;; (smtpmail-stream-type 'ssl)
  ;; (smtpmail-smtp-server "smtp.qq.com")
  ;; (smtpmail-smtp-service 465)
  ;; (user-mail-address "subdudu@example1.com") ; 你的 mail 地址
  ;; (user-full-name "XXX") ; 你的全名
  ;; 设置接收邮件 - 使用 mbsync
  (mu4e-get-mail-command "mbsync -a") ; 执行这个命令来拉取邮件
  (mu4e-attachment-dir "~/Downloads") ; 附件保存目录
  ;; 其它设置
  (mu4e-view-show-images t) ; 在邮件中显示图片
  (mu4e-use-fancy-chars t) ; 使用漂亮的图标
  :init
  ;; 每60分钟同步一次,没有设置mu4e-update-interval来同步邮件,它只在mu4e运行中才能起作用
  (run-with-timer 60 (* 30 60) '(lambda () (mu4e-update-mail-and-index t)))
  :config
  (setq mu4e-contexts         ;多邮件账户发件环境配置
        `(,(make-mu4e-context
            :name "example1"
            :enter-func (lambda () (mu4e-message "Entering example1 context"))
            :leave-func (lambda () (mu4e-message "Leaving example1 context"))
            :match-func (lambda (msg)
                          (when msg
                            (string-prefix-p "/qq" (mu4e-message-field msg :maildir))))
            :vars '((user-mail-address . "subdudu@example1.com")
                    (user-full-name . "XXX")
                    (message-user-organization . "Private")
                    (smtpmail-stream-type . ssl)
                    (smtpmail-smtp-server . "smtp.qq.com")
                    (smtpmail-smtp-service . 465)
                    (mu4e-sent-folder . "/qq/Sent Messages")
                    (mu4e-drafts-folder . "/qq/Drafts")
                    (mu4e-trash-folder . "/qq/Deleted Messages")
                    (mu4e-refile-folder . "/qq/其他文件夹/归档")
                    ))
          ,(make-mu4e-context
            :name "work"
            :enter-func (lambda () (mu4e-message "Entering work context"))
            :leave-func (lambda () (mu4e-message "Leaving work context"))
            :match-func (lambda (msg)
                          (when msg
                            (string-prefix-p "/work" (mu4e-message-field msg :maildir))))
            ;; (lambda (msg)
            ;;   (when msg
            ;;    (mu4e-message-contact-field-matches msg :to "subdudu@example3.com")))
            :vars '((user-mail-address . "subdudu@example3.com")
                    (user-full-name . "XXX")
                    (message-user-organization . "XXXXXX")
                    (smtpmail-stream-type . ssl)
                    (smtpmail-smtp-server . "smtp.exmail.qq.com")
                    (smtpmail-smtp-service . 465)
                    (mu4e-sent-folder . "/work/Sent Messages")
                    (mu4e-drafts-folder . "/work/Drafts")
                    (mu4e-trash-folder . "/work/Deleted Messages")
                    (mu4e-refile-folder . "/work/其他文件夹/归档")
                    ))
          ,(make-mu4e-context
            :name "aliyun"
            :enter-func (lambda () (mu4e-message "Entering aliyun context"))
            :leave-func (lambda () (mu4e-message "Leaving aliyun context"))
            :match-func (lambda (msg)
                          (when msg
                            (string-prefix-p "/ali" (mu4e-message-field msg :maildir))))
            :vars '((user-mail-address . "subdudu@example2.com")
                    (user-full-name . "XXX")
                    (message-user-organization . "Private")
                    (smtpmail-stream-type . ssl)
                    (smtpmail-smtp-server . "smtp.example2.com")
                    (smtpmail-smtp-service . 465)
                    (mu4e-sent-folder . "/ali/已发送")
                    (mu4e-drafts-folder . "/ali/草稿")
                    (mu4e-trash-folder . "/ali/已删除邮件")
                    (mu4e-refile-folder . "/ali/ReservedLetters")
                    ))))
  )

经过上述配置,就可以使用 mail 或者 mu4e 来发邮件了。配置里把收取邮件的配置也写进去了,同步邮件使用的是 mbsync

2 邮件收取

deepseek 推荐的工具包括 3 类:

  • mu4e:基于外部工具 maildir-utils(通常被称为 mu,maildir 是一种邮件本地存储格式)和 isync(邮件同步,支持 maildir 和 imap,提供命令行工具 mbsync),优点是离线工作,速度快,搜索功能强大,与 Org-mode 等 Emacs 生态完美结合。
  • GNUS:是 Emacs 自带的超级强大的新闻/邮件阅读器,可以直接通过 IMAP/SMTP 与服务器交互,无需本地工具。不仅支持邮件,还支持新闻组 (NNTP),但学习曲线比 mu4e 更陡峭,在处理大量邮件时速度可能较慢。
  • notmuch:是一个基于本地索引的邮件客户端,与 mu4e 类似,但“只索引,不存储”,搜索能力是其核心。

2.1 mu4e(debian 环境)

我选择了 mu4e 方案,其它方案未尝试,首先安装 mu4e 和 isync 这两个外部工具:

sudo apt install mu4e isync

然后配置 mbsync,mbsync 配置成功了,其它就简单了。mbsnync 配置文件较为复杂,分为若干块,块与块之间用空行隔开。这些块分为 Account, Store 和 channel 3 类,Account 类用于配置邮箱服务器凭证,MaildirStore 指定本地存储位置,IMAPStore 指定邮箱账户,channel 则负责将远程与本地连接起来,并指定同步哪些文件夹,如何同步。该配置文件位于 $XDG_CONFIG_HOME/isyncrc ,如果没有就创建一个:

IMAPAccount example1
  Host imap.qq.com
  User subdudu@example1.com
  TLSType IMAPS
  AuthMechs LOGIN
  PassCmd "gpg --quiet --decrypt ~/.authinfo.gpg 2>/dev/null | awk '/smtp\.qq\.com/ {print $8}'"

IMAPStore example1-remote
  Account example1

MaildirStore example1-local
  Path ~/.local/share/MailDir/qq/
  INBox ~/.local/share/MailDir/qq/Inbox/
  SubFolders Verbatim

Channel example1-inbox
  Far :example1-remote:
  Near :example1-local:
  Sync All
  Patterns *
  Create Both
  Expunge None
  SyncState *

IMAPAccount aliyun
  Host imap.example2.com
  User subdudu@example2.com
  TLSType IMAPS
  AuthMechs LOGIN
  PassCmd "gpg --quiet --decrypt ~/.authinfo.gpg 2>/dev/null | awk '/smtp\.aliyun\.com/ {print $8}'"

IMAPStore aliyun-remote
  Account aliyun

MaildirStore aliyun-local
  Path ~/.local/share/MailDir/ali/
  INBox ~/.local/share/MailDir/ali/Inbox/
  Trash ~/.local/share/MailDir/ali/已删除邮件/
  SubFolders Verbatim

Channel aliyun-inbox
  Far :aliyun-remote:
  Near :aliyun-local:
  Sync All
  Patterns *
  Create Both
  Expunge None
  CopyArrivalDate yes
  SyncState *

IMAPAccount work
  Host imap.exmail.qq.com
  User subdudu@example3.com
  TLSType IMAPS
  AuthMechs LOGIN
  PassCmd "gpg --quiet --decrypt ~/.authinfo.gpg 2>/dev/null | awk '/smtp\.exmail\.qq\.com/ {print $8}'"

IMAPStore work-remote
  Account work

MaildirStore work-local
  Path ~/.local/share/MailDir/work/
  INBox ~/.local/share/MailDir/work/Inbox/
  SubFolders Verbatim

Channel work-inbox
  Far :work-remote:
  Near :work-local:
  Sync All
  Patterns *
  Create Both
  Expunge None
  SyncState *

配置好之后,在终端执行以下命令,即可将邮件同步到本地:

mkdir -p ${XDG_DATA_HOME:-$HOME/.local/share}/MailDir/qq
mkdir -p ${XDG_DATA_HOME:-$HOME/.local/share}/MailDir/ali
mkdir -p ${XDG_DATA_HOME:-$HOME/.local/share}/MailDir/work
mbsync example1-inbox

mbsync 的配置相当复杂,现在还没完全搞明白。反正先拣最重要的“邮箱服务器凭证”和“本地路径”配置好,其它的几乎套模板,没有改动,就能够成功收取邮件了。如果有个性化的需求,可能需要配置其它选项。

接下来是 mu 初始化,根据 XDG Base Directory 规范,我们最好把目录设置在 $XDGDATAHOME 下:

mu init --maildir=${XDG_DATA_HOME:-$HOME/.local/share}/MailDir --my-address=subdudu@example1.com  --my-address=subdudu@example2.com --my-address=subdudu@example3.com

然后在 Emacs 中执行 mu4e 函数,就能调了 mu4e 界面了。根据界面提示操作即可。

mu4e 有几个快捷键, dmu4e-view-mark-for-trashmu4e-headers-mark-for-trash )将邮件移动到垃圾箱,并打开 T 标记, rmu4e-view-mark-for-refilemu4e-headers-mark-for-refile )将邮件归档,这些操作相当于给邮件打上标签,这个标签在界面上可以看到,在实际应用( mu4e-mark-execute-allmu4e-view-marked-execute )前这些标签都可以按 u 移除。

mu4e 可以批量操作邮件,使用 * 给邮件打上“*”标记,然后可以批量执行某个命令,或者使用 % 进行批量操作。

mu4e 可以快速搜索邮件,搜索可以使用正则表达式,如: subject:/退款/, from:/淘宝.+/, body:/read/, /我/ 等。

2.2 后记

在配置多邮箱时,最新的方法是使用 mu4e-context 实现邮箱配置的自动切换。在配置的过程尝试过将 mu4e-sent-folder 等目录设置为一个如下的函数,但发邮件的时候就出问题了,因为 to 字段还没有值,而且有些邮件的 to 字段根本就没有值,用的是 bcc 字段,所以这个方法行不通:

(mu4e-sent-folder (lambda (msg)
                    (cond
                     ((mu4e-message-contact-field-matches msg :to "subdudu@example1.com")
                      "/qq/Sent Messages")
                     ((mu4e-message-contact-field-matches msg :to "subdudu@example3.com")
                      "/work/Sent Messages"))))

这个问题也提醒了我,邮件不一定都有 to 字段,bcc 字段是看不到的,所以在 mu4e-context 设置时我把判断 to 字段修改成了判断 maildir 。

后来想设置一个 default 的 context,但在发送邮件的时候就直接进入到这个 context,没有 user-mail-address:

(make-mu4e-context
 :name "default"
 :enter-func (lambda () (mu4e-message "Entering default context"))
 :leave-func (lambda () (mu4e-message "Leaving default context"))
 :match-func (lambda (msg) t)
 :vars '((mu4e-sent-folder . "/sent")
         (mu4e-drafts-folder . "/drafts")
         (mu4e-trash-folder . "/trash")
         (mu4e-refile-folder . "/archive")))

3 通讯录

(use-package bbdb
  :ensure t
  :bind ((:map mu4e-view-mode-map
               ("C-c b" . bbdb-mua-edit-sender))
         (:map mu4e-compose-mode-map
               ("M-TAB" . bbdb-complete-name)))
  :custom
  (bbdb-mua-auto-update-p 'query)
  (bbdb-file "~/.emacs.d/bbdb")
  :config
  (bbdb-initialize 'mu4e 'message)

  )

4 gnus

Gnus 是 Emacs 的官方包,其主要目标就是阅读新闻,它会把 usenet, email, rss, atom 甚至搜索结果这些看起来相似度极低的服务都作为新闻源。首先设置 Gnus 的工作目录:

(use-package gnus
  :ensure nil
  :defer t
  :custom
  (gnus-directory
   (expand-file-name "News" (or (getenv "XDG_DATA_HOME")
                                "~/.local/share/")))
  (gnus-select-method '(nntp "news.gwene.org"))  ;; RSS 聚合的新闻组
  )

5 读取 RSS 新闻

阅读 RSS 使用的是 elfeed,它的配置相比邮件就简单多了。

(use-package elfeed
  :ensure t
  :defer t
  :bind ((:map elfeed-show-mode-map
               ("RET" . elfeed-show-visit))
         (:map elfeed-search-mode-map
               ("C-RET" . elfeed-search-browse-url)))
  :custom
  (elfeed-feeds
   '("https://feeds.maketecheasier.com/MakeTechEasier"
     "https://solar.lowtechmagazine.com/feeds/all-en.atom.xml"
     "https://old.reddit.com/r/f1technical.rss"
     "https://www.debian.org/News/news"
     "https://paper.seebug.org/rss/"
     "https://openlanguage.com/ez/podcast/learn-english/pj"
     "http://www.stats.gov.cn/sj/zxfb/rss.xml"
     "http://www.stats.gov.cn/sj/sjjd/rss.xml"
     "https://www.chinanews.com.cn/rss/importnews.xml"
     "https://rss.aishort.top/?type=wasi" ;瓦斯阅读
     "https://planet.emacslife.com/atom.xml"
     ))
  (line-spacing 0.3)          ;增加行距
  (elfeed-show-truncate-long-urls nil)
  :init
  (run-at-time 70 (* 60 60) 'elfeed-update) ;每小时刷新一次
  )

阅读前一定要 elfeed-updateG )刷新,它不会自动刷新。阅读的时候只能看到摘要,看全文要用到浏览器,使用 elfeed-show-visit 可以不用鼠标就能在浏览器打开链接。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐