ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

himalaya 邮件发送副本保存:`message.send.save-copy` 配置解析与“先发送后保存“语义

himalaya 邮件发送副本保存:`message.send.save-copy` 配置解析与“先发送后保存“语义 CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载本文围绕 himalaya v2 新增的message.send.save-copy配置项展开讲解如何为每个账户配置发送邮件时自动留存副本的邮箱、如何使用--no-save跳过单次副本、以及底层先发送、后保存的执行顺序。读完本文你将掌握该配置的全局/按账户写法、与--save/--no-save的优先级关系、v1 布尔值的兼容方式以及背后的源码实现与测试验证依据。背景v2 为什么重新引入保存副本配置在 himalaya v1 中message.send.save-copy当时名为email-sending-save-copy用于控制发送邮件时是否把副本存入sent邮箱。v2 早期版本移除了这一配置导致通过 SMTP 发送的邮件除非每次调用都显式传--save否则不会在任何地方留存副本。这与邮件客户端的常规预期相悖用户希望发出的信能在已发送目录里找到。从 cairn/changes/message-send-save-copy/proposal.md 可以看到v2 移除该配置后带来的问题是多重的每个前端himalaya-emacs、himalaya-vim、himalaya-tui都不得不各自发明一个保存副本的选项而且它们并不知道某个账户实际通过哪个后端发送副本需求本身是按账户不同的Gmail 和 Microsoft Graph 会在服务端自行归档已发送邮件而 SMTP 不会更关键的是旧版--save在发送之前就先追加副本一旦发送失败就会留下一条从未离开本机的邮件副本。本次变更正是针对这些痛点让副本保存成为可配置的账户级默认行为并修正执行顺序。配置项message.send.save-copy取值形式与语义message.send.save-copy可写在全局配置表或某个账户的[accounts.name]表下作为发送命令未显式传--save时的默认副本邮箱。它接受三类值见 src/config.rs 中的SaveCopyConfig枚举serde(untagged)反序列化取值含义示例true存到sent角色对应的邮箱等价于写sentmessage.send.save-copy truefalse不保存副本message.send.save-copy false字符串一个邮箱名、别名或角色解析方式与--save相同message.send.save-copy Sent Items字符串形式遵循 v1 语义的延续true代表sent邮箱false代表不保存因此旧版 v1 配置中的布尔值在 v2 中依然按原意读取。该兼容性由 src/account/context.rs 的测试save_copy_reads_a_v1_boolean_beside_v1_keys覆盖全局配置写message.send.save-copy true时resolve_save解析为sent账户级写false覆盖全局后解析结果为None不保存。全局与按账户的合并规则配置结构上message.send.save-copy位于MessageConfig→MessageSendConfig两级结构中见 src/config.rs# 全局表 [message.send] save-copy sent # 某个账户 [accounts.gmail] message.send.save-copy falseMessageConfig特意未启用deny_unknown_fields对比同文件中其他配置结构都启用了该属性这样 v1 的[message]表里其余键如read、write、delete等在 v2 中依然能继续加载不会因未知键报错。账户级配置通过Account::merge覆盖全局值save_copy: other.save_copy.or(self.save_copy)见 src/account/context.rs。官方配置示例config.sample.toml 给出了全局写法# Mailbox a sent message is copied to when --save is not passed, --no-save # skipping it for one send. A name, alias or role; true stands for sent. # Leave it unset on Gmail and Microsoft Graph, which file the sent message # themselves. #message.send.save-copy sent账户级写法见 config.sample.toml[accounts.example] # Per-account overrides for the global options above. #message.send.save-copy sent注意示例中的关键提示Gmail 与 Microsoft Graph 账户应保持该项不设置因为这两个后端在服务端会自行归档已发送邮件不需要本地再存一份。命令行为--no-save与解析优先级新增的--no-save参数message send、message compose、message reply、message forward四个命令新增了--no-save参数用于单次发送跳过配置的副本。它与--save通过 clap 的conflicts_with声明互斥见 src/shared/message/send.rs/// Skip the copy message.send.save-copy configures. #[arg(long, conflicts_with save)] pub no_save: bool,compose/reply/forward三个编写命令中--no-save与--save、--send的组合语义相同分别见 src/shared/message/compose.rs、src/shared/message/reply.rs、src/shared/message/forward.rs。Account::resolve_save统一的解析入口所有发送/编写命令都调用同一个解析函数Account::resolve_save决定最终保存到哪里实现见 src/account/context.rspub fn resolve_savea( a self, over: Optiona str, no_save: bool, send: bool, ) - Optiona str { if over.is_some() || no_save || !send { return over; } match self.save_copy.as_ref()? { SaveCopyConfig::Enabled(true) Some(MailboxRole::Sent.as_str()), SaveCopyConfig::Enabled(false) None, SaveCopyConfig::Mailbox(mailbox) Some(mailbox), } }该函数的优先级规则可以总结为下表条件结果传了--save MAILBOXover有值使用命令行指定的邮箱覆盖配置传了--no-save返回None不保存副本未发送send false如compose不配合--send不自动保存配置的副本仅针对已发送邮件配置为true解析为sent角色邮箱配置为false不保存配置为字符串返回该邮箱名/别名/角色交给后续resolve_mailbox通过别名表解析测试 resolve_save_falls_back_on_the_copy_only_when_sending 精确验证了这组行为配置Sent Items时发送场景解析为Sent Items非发送场景解析为None--save Archive覆盖为Archive--no-save返回None。以message send为例命令执行时把三个参数原样传入见 src/shared/message/send.rslet save account.resolve_save(self.save.as_deref(), self.no_save, true); handler::route(printer, account, client, raw, save, true)命令行速查# 发送并按账户配置保存副本如 save-copy sent himalaya message send message.eml # 发送并保存到指定邮箱覆盖配置 himalaya message send --save Archive message.eml # 发送但本次不保存副本 himalaya message send --no-save message.eml # 编写并发送同时保存副本 himalaya message compose --send --save Sent Items # 配置了副本的账户reply/forward 加 --send 也会自动存副本 himalaya message reply --send --no-save reply-body.txt执行顺序先发送后保存本次变更的另一核心语义是**先发送、后保存**send-before-save。此前--save在发送之前就追加副本发送失败时会在本地留下一条从未真正离开的幽灵副本。统一处理逻辑集中在 src/shared/message/handler.rs 的handler::applylet queued match send { true { let sent mailbox.or_else(|| account.mailbox_alias.get(sent).map(String::as_str)); client.send_message(sent, raw.clone())? } false None, }; let saved_id match mailbox { Some(mailbox) if send Some( client .add_message(mailbox, flags, raw) .with_context(|| format!(Message sent, but saving a copy to {mailbox} failed))?, ), Some(mailbox) Some(client.add_message(mailbox, flags, raw)?), None None, };关键点先调用client.send_message真正把邮件投递出去再调用client.add_message追加副本若发送失败send_message返回错误并中断不会留下副本若发送成功但保存副本失败命令同样以错误退出但错误信息明确说明邮件已发出即Message sent, but saving a copy to mailbox failed——用户不会误以为邮件没发出去而重复发送。这一顺序对--save与--send组合、message send --save、以及message add --send见 src/shared/message/add.rs一视同仁所有既要发送又要保存的路径都走同一个apply。handler::route在此基础上打印结果行见 src/shared/message/handler.rs保存且已发送Message successfully saved and sent仅保存Message successfully saved仅发送Message successfully sent发送被推迟到 pimdir 队列时输出会附带queue_id。保存的副本默认附带\Seen已读标志Flag::from_iana(IanaFlag::Seen)见 src/shared/message/handler.rs副本邮箱名则先经account.resolve_mailbox通过别名表做大小写不敏感解析见 src/account/context.rs所以配置里写别名、角色或后端原生 id 均可。设计取舍与适用边界本次变更的提案文档 proposal.md 与日志 cairn/log/2026-10-01-message-send-save-copy.md 明确记录了两处边界向导wizard不写入该选项。原因是 IMAP 后端要等到imap-special-use-aliases变更落地后才能解析sent角色而向导也不会写mailbox.alias.sent若此时生成save-copy sent每次 IMAP 发送都会失败。因此该配置适合用户在了解自己账户后端行为后手动填写。Gmail 与 Microsoft Graph 不需要设置这两个后端服务端会自动归档已发送邮件配置了反而可能产生重复副本。SMTP 账户才是本配置的主要受益者。对应规范已固化到 cairn/spec/config.md配置 SHALL 为发送命令提供默认副本邮箱、--no-saveSHALL 单次跳过且与--save冲突、message表 SHALL 接受未知键同时 cairn/spec/commands.md 中固化了保存跟随发送的顺序要求发送失败不留副本保存失败须报错且说明邮件已发送。变更落地与验证该变更已进入 CHANGELOG.mdUnreleased → AddedAddedmessage.send.save-copy, the mailbox a sent message is copied to when--saveis not passed, and--no-saveonmessage send,compose,replyandforwardto skip it. It takes a mailbox name, alias or role, and the v1truestill reads as thesentmailbox.任务清单 cairn/changes/message-send-save-copy/tasks.md 显示全部子任务均已完成MessageConfig/MessageSendConfig与save-copy字符串或布尔、合并进Account并由Account::resolve_save解析、四个命令上的--no-save、handler::apply先发送后保存、测试与配置样例与 CHANGELOG、以及最终归入cairn/spec/config.md与cairn/spec/commands.md。验证方式见日志为针对 Maildir 账户 脚本化 SMTP 服务器的端到端测试覆盖了配置副本生效、--no-save跳过、--save覆盖配置、发送被拒绝时不留下副本、保存副本失败时报出邮件已发出、以及compose --send组合场景。单元测试则固化在 src/account/context.rs保证解析逻辑后续不会被无意破坏。小结message.send.save-copy把发送邮件自动留副本从每次调用都要显式传参收敛为账户级配置 单次覆盖参数的组合字符串或布尔取值兼容 v1 习惯--no-save提供单次退出通道resolve_save统一了优先级而handler::apply的先发送、后保存顺序彻底消除了失败发送遗留幽灵副本的问题。对于 SMTP 账户这几乎是开箱即用的最佳实践对于 Gmail/Graph 账户保持该项未设置即可让服务端接管归档。赞分享CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载相关推荐深入解析 TanStack Table 核心的 memo()依赖追踪记忆化函数及其在表格内部的应用深入解析 TanStack Table 核心的 memo 依赖追踪记忆化函数及其在表格内部的应用 本文聚焦 TanStack Table本仓库 ta/tabCLIFrappe 邮件副本功能解析send_me_a_copy 给自己发送出站邮件副本的实现与演进Frappe 邮件副本功能解析send_me_a_copy 给自己发送出站邮件副本的实现与演进 导读 本文基于 Frappe 仓库 v5 变更日志 https后端Web框架低代码前端认证鉴权5 分钟快速上手 hoist-non-react-statics提升组件静态属性的完整教程5 分钟快速上手 hoist non react statics提升组件静态属性的完整教程 在 React 高阶组件开发中你是否遇到过静态属性丢失的困扰h上一篇Android权限请求状态保存PermissionsDispatcher与SavedStateHandle下一篇大模型微调全攻略LLMs千面郎君中的SFT与PEFT技术详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表