2025-02-21

Emacsでメールファイル(.eml等)をそのまま読む

昔から疑問に思っていて未だによく分かっていないことなのですが、Emacsから1件のメールのデータが入ったファイルを開いてその内容を読むにはどうしたら良いのでしょうか。

Web検索でちょっと調べてみても、既存のメールを取り扱うEmacs用のシステムに取りこんで読めというようなことばかりが引っかかり、単純に1件のメールだけが入ったファイルを普通のファイルを開くように読む方法がなかなか出てこないんですよね。

ここで言うメールが入ったファイルというのは沢山のメールが一つに詰め込まれたmbox形式とかではなくて、一般的なメールクライアントがメッセージを外部ファイルとして保存するときに.emlという拡張子を付けて出力するような先頭にメールヘッダーが入っているようなファイルのことです(ええと何形式というんだっけ)。内容はMIMEでエンコードされていて普通はそのままでは読めません(……ひょっとしてASCII言語圏ではそのまま読めるのでしょうか? だからあまり問題になっていない?)。

私は普段Emacs上ではWanderlustを使っているのですが、それが依存しているsemiというライブラリにMIMEエンコードされたバッファをプレビューするコマンドが含まれています。

その名もmime-view-buffer。メールファイルを開いて M-x mime-view-buffer を実行すれば、メールの内容が 別バッファに 人間が読める状態で表示されます。素晴らしい。

ちなみに検索して見つかった別の方法としては次のものがあります。これはGnusの関数(gnus-article-prepare-display)を使います。

Any way to just render an email file on disk? : r/emacs

(defun my/render-mime-message ()
  "Render the current buffer as a Gnus article."
  (interactive)
  (gnus-article-prepare-display))

これで読めると言えば読めるのですが、どうにも釈然としません。普通にEmacsからファイルを開く要領で読めるようにならないのでしょうか。要するにfind-fileで画像ファイルやpdfファイルを開いたら中身のバイナリではなく人間が読めるようなものが出るのと同じようにしたいわけです。

と、今更そのようなことを思い出したのは、先日いくつかのメールへのリンクをorg-modeに書きたくなって久しぶりにol-wl.elを使ったのがきっかけでした。メールへのリンクをorg-mode文書に書きたくなることはほとんどなく、近年はorg-contribが別パッケージに分かれたこともあってインストールすらされていませんでした。ol-wl.elを使うと wl: リンクタイプが追加されるのですが、そのパスにはWanderlustが管理するフォルダ表記とメッセージIDを指定します。そのリンクをC-c C-oで開くとWanderlustが起動してその中でメールの内容が表示されます。それを見て、単純に file: リンクタイプでメールが入っているファイルへリンクしたらダメなのかな? と思ったわけです。

画像やPDFを開くノリでできないかと思ったのですが、あれはバッファ全体をdisplayテキストプロパティ(またはオーバーレイプロパティ)で画像に置き換えることで解決しています。今回やりたいのは画像では無くテキストで置き換えること。displayプロパティで別のテキストに置き換えるとそのテキストの中にポイントを置けないのであまり望ましくないでしょう。なので、どちらかと言えばhexl-modeがファイルの中身をHEXダンプに置き換える時の手法が近そうです。

ただ、これはちゃんと実装しないと置き換えた後のテキストで元のファイルを上書きしてしまうリスクがあります。

とりあえず今回必要だったのはorg-modeからリンクを張ることなので、必ずしも律儀にそのファイルに関連付けられたバッファで開く必要はありません。org-modeではリンクを開くときの動作を条件毎にカスタマイズできるようになっているので、そこでmime-view-bufferで開くような関数を指定してしまえば良いのです。

(defun my-mime-view-file (file _original-link-path)
  "mime-view-bufferを使ってメールFILEのプレビューを開く。"
  (with-temp-buffer
    ;; PreviewバッファのカレントディレクトリはFILEがある場所にする。
    (setq default-directory
          (file-name-directory (expand-file-name file)))
    (insert-file-contents file)
    (mime-view-buffer
     nil
     ;; バッファ名にファイル名を入れる。
     (format "*MIME View: %s*" (file-name-nondirectory file)))))

;; Wanderlust用のメールが格納されているディレクトリにあるファイルを開
;; くときは my-mime-view-file を使う(例)。
(add-to-list 'org-file-apps '("c:/my-wl-mail-dir/" . my-mime-view-file)))

後は [[file:c:/my-wl-mail-dir/inbox/123]] のようなリンクを書けば、それをC-c C-oで開こうとすると上の関数の働きによって人間が読めるものがすぐに開くというわけです。

とりあえずこれでお茶を濁しておきますが、そのうちhexl-mode的な手法で内容を表示するmajor-modeを作りたい所。

メールが入ったファイルを読むだけのことがなんでこんなに面倒なんだろう。