本文目錄
處理郵件問題時,常常需要拿到某封信的原始內容(完整的表頭、MIME 結構、附件),例如追查寄件來源、檢查 SPF/DKIM 結果,或是要把信件交給資安單位分析。
在 Zimbra 上用指令撈信件,第一個想到的通常是 zmmailbox 或 zmsoap,但實際用起來會遇到兩個狀況:zmmailbox 只顯示整理過的文字,而 zmsoap 的 GetMsgRequest 有時候會回傳內容、有時候只回傳一個網址。這篇整理取得郵件原始內容的幾種方法。
先找到郵件 ID
所有方法都需要郵件在信箱裡的 ID。如果知道 Message-ID,可以用 zmsoap 搜尋:
su - zimbra
zmsoap -z -m user@example.com -t mail SearchRequest \
@types=message \
@query='field[Message-ID]:"<1234567890.12.1762871492739.JavaMail.zimbra@example.com>"'
-z:以 Zimbra 管理者身分驗證(使用本機的 zimbra 帳號設定),不用輸入密碼-m:要操作的信箱-t mail:使用 mail 命名空間的 SOAP 請求
回應裡的 <m ... id="852474"> 就是郵件 ID:
<SearchResponse offset="0" more="0" sortBy="dateDesc" xmlns="urn:zimbraMail">
<m s="2206" d="1762871492000" id="852474" l="5" cid="852587">
<e p="User" a="user@example.com" t="f"/>
<su>測試郵件</su>
</m>
</SearchResponse>
也可以用一般的搜尋語法,例如依主旨、寄件者與日期:
zmmailbox -z -m user@example.com search -t message 'subject:"測試郵件" from:sender@example.com after:11/1/2025'
zmmailbox gm:只有整理過的文字
zmmailbox -z -m user@example.com gm 852261
Id: 852261
Folder: /Sent
Subject: Re: 測試郵件
From: User <user@example.com>
To: Partner <partner@example.org>
Date: Tue, 11 Nov 2025 09:05:42 +0800 (CST)
Flags: FROM_ME, ATTACHED
Size: 673.49 KB
(郵件內文)
gm(getMessage)顯示的是解析過的結果:主要表頭與純文字內文,看不到完整的表頭(Received、Authentication-Results 等),也沒有附件內容,拿來快速看信可以,要分析就不夠了。
zmsoap GetMsgRequest raw=1
zmsoap -z -m user@example.com -t mail GetMsgRequest m @id=852261 @raw=1
小封的信,回應裡會直接包含原始內容。但大一點的信會變成這樣:
<GetMsgResponse xmlns="urn:zimbraMail">
<m s="689650" d="1762823142000" f="sa" id="852261" l="5" cid="764333">
<content url="/service/content/get?id=852261"/>
</m>
</GetMsgResponse>
這不是錯誤。Zimbra SOAP API 文件說明,raw=1 時如果郵件太大或內容不是純 ASCII,就改回傳 content servlet 的網址,要另外用這個網址去下載。上面這封是 673 KB、帶附件的信,就走了網址的方式;中文郵件即使不大也可能如此。
GetMsgRequest 也有一個 useContentUrl=1 參數,可以強制一律回傳網址,寫腳本時行為比較一致。
透過 content servlet 下載
拿到網址之後,要帶著該信箱使用者的驗證權杖去下載。
取得管理者權杖
ADMIN_TOKEN=$(zmsoap -a admin@example.com -p 'your-admin-password' AuthRequest \
| sed -n 's:.*<authToken>\(.*\)</authToken>.*:\1:p')
注意:密碼直接寫在指令裡會留在 Shell 的歷史紀錄中,執行完記得清掉,或改成執行時才輸入。用完的權杖也不要外流,它在有效期間內等同管理者身分。
以管理者身分代理成該使用者
用 DelegateAuthRequest 取得目標信箱的使用者權杖,送到管理通訊埠 7071:
USER_TOKEN=$(curl -s -k \
-H "Content-Type: application/soap+xml" \
-d '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header><context xmlns="urn:zimbra"><authToken>'"$ADMIN_TOKEN"'</authToken></context></soap:Header>
<soap:Body><DelegateAuthRequest xmlns="urn:zimbraAdmin"><account by="name">user@example.com</account></DelegateAuthRequest></soap:Body>
</soap:Envelope>' \
https://localhost:7071/service/admin/soap/ \
| sed -n 's:.*<authToken>\(.*\)</authToken>.*:\1:p')
下載原始郵件
curl -s -k -H "Cookie: ZM_AUTH_TOKEN=$USER_TOKEN" \
"https://mail.example.com/service/content/get?id=852261&fmt=raw" > 852261.eml
網頁信箱的通訊埠如果改過(不是 443),網址要跟著改。下載下來的 .eml 就是完整的原始郵件,可以直接用郵件軟體開啟,或用文字編輯器看表頭。
最簡單的方式:zmmailbox getRestURL
其實不用自己處理權杖,zmmailbox 的 getRestURL(縮寫 gru)可以直接透過 Zimbra 的 REST 介面取得原始郵件:
zmmailbox -z -m user@example.com gru "//?id=852261" > 852261.eml
-z 會以管理者身分代理該信箱,大小、編碼都不用管。只是要撈一封信看原始內容的話,我現在都用這個方式;需要寫進腳本、和其他 SOAP 請求整合時,再用前面的 content servlet 做法。
方法比較
| 方法 | 取得內容 | 適合 |
|---|---|---|
zmmailbox gm | 解析過的表頭與文字 | 快速看信 |
zmsoap GetMsgRequest raw=1 | 小封信的原始內容,大封信只有網址 | 寫腳本時判斷用 |
| content servlet + 權杖 | 完整原始內容 | 腳本整合、遠端呼叫 |
zmmailbox gru | 完整原始內容 | 手動取信最方便 |
注意事項
- 隱私:能以管理者身分讀取任何人的信件,等於握有全公司的郵件內容。調閱信件前要有正當理由與授權程序,並留下紀錄
- 權杖:
ADMIN_TOKEN、USER_TOKEN都等同登入狀態,不要寫進檔案或貼到工單裡 - 憑證:範例裡的
curl -k是略過憑證檢查,只適合在主機本機或測試環境使用
結論
GetMsgRequest 回傳網址不是壞掉,是 Zimbra 對大郵件與非 ASCII 內容的設計。要拿原始郵件,手動用 zmmailbox gru 最簡單,寫腳本則用 content servlet 搭配代理權杖。
參考資料
- Zimbra SOAP API Reference:GetMsgRequest
https://files.zimbra.com/docs/soap_api/9.0.0/api-reference/zimbraMail/GetMsg.html - Zimbra SOAP API Reference:DelegateAuthRequest
https://files.zimbra.com/docs/soap_api/9.0.0/api-reference/zimbraAdmin/DelegateAuth.html - Zimbra 常用 CLI 指令整理
https://blog.jason.tools/2021/05/zimbra-cli.html



