
MailFlare 附件全鏈路解析從 MIME 拆包到 R2 存儲再到帶鑒權的下載與 inline 圖片【免費下載鏈接】mailflareEmail client with custom domain based on Cloudflare項目地址: https://gitcode.com/gh_mirrors/mai/mailflareMailFlare 是一款基于 Cloudflare 構建的自托管郵箱客戶端而附件處理正是其中最復雜的一環一封帶圖郵件從收到、拆開、存到 R2 對象存儲再到用戶下載或正文中內聯顯示圖片中間橫跨解析、存儲、鑒權、渲染四層鏈路。本文帶你完整走一遍這條鏈路看懂每一步在做什么、為什么這么做。一、附件全鏈路的四個環節 把一條附件的生命周期拆開看就是四步環節做什么核心文件收信拆包原始郵件落 R2MIME 解析出附件src/lib/email/inbound.ts、src/lib/email/parse.ts存儲歸檔附件逐個寫入 R2元數據入庫src/lib/email/attachments.ts帶鑒權下載Cookie 登錄態 郵箱訪問權限校驗src/app/api/messages/[messageId]/attachments/[attachmentId]/route.tsinline 渲染正文cid:引用替換為鑒權預覽地址src/app/(dashboard)/inbox/[messageId]/utils.ts二、收信原始郵件先落 R2再 MIME 拆包一封外部來信到達后worker.ts不會直接解析正文而是先調用storeRawToR2把完整的 EML 原始字節以inbound/{時間戳}-{id}.eml為鍵存入 R2見src/lib/email/inbound.ts隨后丟進隊列異步處理。processInboundMessage從 R2 取回原始郵件交給parseRawMime見src/lib/email/parse.ts。它基于 postal-mime 庫完成真正的 MIME 拆包提取主題、純文本/HTML 正文、發件人等頭部信息把email.attachments逐個規整成統一的AttachmentContent結構文件名、MIME 類型、內容字節自動處理 base64 解碼、inline/attachmentdisposition以及關鍵的contentId即 CID正文內嵌圖片靠它定位無名附件自動命名為attachment-1、attachment-2未知類型兜底為application/octet-stream。原始郵件保留在 R2 還有一個用途郵件詳情頁據此生成退訂鏈接。三、存儲附件寫 R2元數據進 D1失敗自動回滾拆出的附件由storeMessageAttachments見src/lib/email/attachments.ts歸檔鍵名結構attachments/{messageId}/{attId}/{filename}按消息隔離便于整封郵件清理文件名消毒路徑分隔符、反斜杠、空字節統一替換為下劃線防止越權路徑元數據入庫D1 的message_attachments表只存文件名、類型、大小、disposition、CID、R2 鍵名等輕量字段二進制本體始終在 R2數據庫不膨脹失敗回滾批量寫入中途出錯時已上傳的 R2 對象會被逐一刪除避免產生孤兒文件。發信走同一套存儲函數但先過一道validateAttachments硬限制單個附件 ≤ 10 MB、一封合計 ≤ 20 MB、最多 10 個附件超限時直接報錯拒絕而不是截斷發送。四、發信inline 圖片在這里第一次上崗sendEmail見src/lib/email/send.ts把消息、正文、附件落庫后通過 Cloudflare Email 服務真正發出去。發信時對附件做了精細區分帶contentId且 disposition 為inline的附件比如正文里的一張配圖會以inline Content-ID方式發出——收件方客戶端能用cid:引用在正文位置直接顯示圖片其余一律按普通附件發出。也就是說CID 這套正文內嵌附件機制在發信端就已就位為收信端的 inline 渲染埋下伏筆。五、下載與預覽一個接口三種模式 附件訪問入口是GET /api/messages/{messageId}/attachments/{attachmentId}。這個接口的設計值得細看它同時解決了安全和體驗兩個問題。先鑒權再談文件見src/app/api/messages/[messageId]/attachments/[attachmentId]/route.ts與src/lib/email/attachments.ts中的getAttachmentForUser未登錄無有效 Cookie直接 401查詢消息歸屬若是共享郵箱調用getMailboxAccessLevel校驗當前用戶是否有讀權限沒有則 404附件必須同時匹配附件 ID 與消息 ID杜絕跨消息越權取件。再用查詢參數切換模式?download1→ 強制下載?preview1且類型可預覽 → 內聯展示附件本身 disposition 為inline→ 內聯展示這正是 inline 圖片的通道。可預覽由isPreviewableAttachmentType判定見src/app/api/messages/[messageId]/attachments/[attachmentId]/utils.tsPDF、音頻、視頻、圖片刻意排除 SVG 以防腳本注入、純文本、JSON、XML、CSV 都算其余一律走下載。響應頭里還有兩道安全保險X-Content-Type-Options: nosniff 嚴格的Content-Security-Policy含sandbox瀏覽器不敢自作聰明把文件當腳本執行Cache-Control: private, max-age3600——只允許當前登錄用戶緩存一小時多用戶共享部署下不會串號。六、inline 圖片把cid:翻譯成帶 Cookie 的 URL ?收件時正文里的內嵌圖片長得像img srccid:abc123img瀏覽器根本打不開這種地址。MailFlare 在頁面渲染前用resolveInlineAttachmentUrls見src/app/(dashboard)/inbox/[messageId]/utils.ts做一次替換從附件元數據里取出contentId去掉尖括號把 HTML 正文中的cid:{contentId}全部替換為/api/messages/{messageId}/attachments/{attId}?preview1。由于該接口認的是登錄 Cookie而img標簽默認就攜帶同源 Cookie圖片于是無感地在正文原位置渲染出來——這就是上一節 disposition 為inline的通道發揮作用的場景。前端則由兩個組件完成最后一步體驗src/components/message-attachment-card.tsx渲染附件卡片圖標、文件名、大小、下載按鈕src/components/message-attachment-viewer.tsx點擊卡片彈出預覽器圖片/PDF/音視頻直接內嵌播放文本類附件通過authFetch拉取內容展示不支持的類型優雅降級為下載。七、小結MailFlare 的附件模塊做對了三件關鍵的事?二進制與元數據分離文件本體進 R2、描述進 D1存儲成本與查詢性能兼得?全鏈路鑒權下載、預覽、inline 圖片全部走同一個帶登錄態校驗的 API共享郵箱場景下按讀權限逐人把關?CID 機制閉環發信端以 inlineContent-ID 發出收信端把cid:翻譯成鑒權預覽 URL正文圖片收發都能原位顯示。從worker.ts收到原始郵件那一刻到你在瀏覽器里點開附件卡片這條鏈路只用了四個核心模塊。如果你想動手看源碼建議按本文第二節到第六節的文件路徑順序閱讀十分鐘即可跑通整個心智模型。【免費下載鏈接】mailflareEmail client with custom domain based on Cloudflare項目地址: https://gitcode.com/gh_mirrors/mai/mailflare創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考