资讯动态

Himalaya 邮箱角色(Mailbox Role)机制详解:跨后端识别收件箱、已发送与垃圾箱的正确姿势

发布时间:2026/10/5 6:33:30 来源:尧图企业网站定制
CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载本文基于 Himalaya 项目Rust 编写的跨协议邮件 CLI落地于 2026-09-29 的mailbox-role变更系统讲解共享 API 上的特殊用途邮箱special-use mailbox识别机制从MailboxRole类型定义、各后端原生角色来源、mailbox.alias.role别名覆写到-m/--mailbox的先 id、后名称、再角色三级解析链。读完你将理解 UI 层如何不依赖名称字符串就能定位 inbox/sent/trash以及如何在配置中修正服务器错误的角色标注。为什么需要邮箱角色名称匹配的三大失败场景在 mailbox-role 变更落地前Himalaya 的共享Mailbox类型不携带任何角色信息UI如 himalaya-tui只能通过匹配inbox之类的字符串来定位特殊邮箱。这一做法在三种场景下必然失效对应 GitHub issue himalaya#743 的诉求JMAP 不透明 idJMAP 服务端返回的邮箱 id 是任意不透明字符串无法从 id 猜出这是收件箱本地化名称法语环境的 Envoyés、中文环境的 已发送用英文关键字sent匹配不到无默认邮箱概念正如 变更提案 所强调的——There is no default mailbox as such, only roles不存在默认邮箱只有角色。大多数后端原生暴露角色别名只是给无法暴露角色的后端提供的兜底。因此本变更的核心动作是让共享Mailbox携带role: OptionMailboxRole由各后端从自己的原生来源填充并在mailbox list中以 ROLE 列展示。完整变更记录见 2026-09-29-mailbox-role.md。共享 API 上的 MailboxRole 类型角色类型定义于 src/email/mailbox.rsMailbox结构体新增了可选字段pub struct Mailbox { pub id: String, // 后续命令引用邮箱用的标识符 pub name: String, // 人类可读名称 pub role: OptionMailboxRole, // 特殊用途角色后端或 mailbox.alias.role 提供 pub total: Optionu64, // 邮件总数可选 pub unread: Optionu64, // 未读数可选 }MailboxRole枚举镜像了IANA IMAP Mailbox Name Attributes 注册表同时被 JMAP 邮箱角色RFC 8621与 IMAP SPECIAL-USE 属性RFC 6154共享枚举变体线上拼写小写含义Inboxinbox新邮件到达的邮箱Allall容纳所有邮件的虚拟邮箱Archivearchive归档邮件存放处Draftsdrafts未发送邮件存放处Flaggedflagged已加星邮件聚集处Importantimportant重要邮件聚集处Junkjunk解析时spam亦映射到它垃圾邮件聚集处Sentsent已发送邮件存放处Subscribedsubscribed已订阅邮箱的虚拟集合Trashtrash已删除邮件存放处Other(String)原样保留注册表不识别的未知角色按原样透传关键解析逻辑MailboxRole::parse对大小写不敏感并会剥离一个前导的\——这正是 IMAP SPECIAL-USE 属性如\Sent的线上写法因此 IMAP 属性可以直接喂给parse。序列化时统一输出小写线上拼写单元测试role_serializes_as_its_wire_spelling验证了Junk序列化为junk、\Custom这类未知角色原样保留。role字段同时出现在mailbox list的表格与--json输出中。各后端的原生角色来源角色优先来自后端原生能力各协议来源在 cairn/spec/backends.md 的 Requirement: Mailbox role 一节有完整定义实现散见于各backend.rsJMAP直接读取role属性JMAP 协议本身定义Mailbox/role属性RFC 8621src/jmap/backend.rs 中mailbox_from将其映射到共享类型role: mailbox.role.map(|role| MailboxRole::parse(role.to_string())),Gmail固定的系统标签 idGmail 没有 role 属性但有所有账户共享的固定 system-label id。src/gmail/backend.rs 的label_role函数按 id 映射fn label_role(id: str) - OptionMailboxRole { match id { INBOX Some(MailboxRole::Inbox), SENT Some(MailboxRole::Sent), DRAFT Some(MailboxRole::Drafts), TRASH Some(MailboxRole::Trash), SPAM Some(MailboxRole::Junk), STARRED Some(MailboxRole::Flagged), IMPORTANT Some(MailboxRole::Important), _ None, } }注意两个细节Gmail 没有 archive 标签归档 失去INBOX标签代码注释明确说明没有 inbox 别名也能通过SENT/TRASH等系统标签解析出角色。Microsoft Graphwell-known 文件夹名解析为 idGraph v1.0 的mailFolders列表不带角色信息因此 src/msgraph/backend.rs 用一张稳定的 well-known 名称表WELL_KNOWN_FOLDERS配合一次$batch请求把名称解析成文件夹 id——这正是为 io-msgraph 新增的能力const WELL_KNOWN_FOLDERS: [(str, MailboxRole); 6] [ (inbox, MailboxRole::Inbox), (sentitems, MailboxRole::Sent), (drafts, MailboxRole::Drafts), (deleteditems, MailboxRole::Trash), (junkemail, MailboxRole::Junk), (archive, MailboxRole::Archive), ];folder_roles()对六个 well-known 名称各发一个GET /me/mailFolders/{name}?$selectid的批量子请求再以返回的 id 对照mailbox list的文件夹集合邮箱缺少的名称如archive自动跳过。IMAP只标记 INBOXIMAP 后端目前只把INBOX标记为 inbox 角色其余邮箱一律None。原因在 变更提案 的 Out of scope 中写明等待 duesee/imap-codec 落地LIST-EXTENDED能力对应 RFC 6154 SPECIAL-USE 属性后再扩展届时 io-imap 才可读取\Sent、\Trash等属性。这与先前 imap-special-use-aliases 变更 的演进方向一致。当前实现中IMAP 的role_mailbox_id在 src/shared/client.rs 里就是一行判断BackendClient::Imap(_) Ok((*role MailboxRole::Inbox).then(|| String::from(INBOX))),在himalaya imap mailbox list子命令中src/imap/mailbox/list.rs 会从服务器返回的ATTRIBUTES如\Sent里提取已知角色展示只是共享 API 层暂不采用。mboxspool 即 inboxmbox 后端把mbox.inbox配置的 spool 当作INBOX展示并标记 inbox 角色src/mbox/backend.rs 的实现为pub fn role_mailbox_id(self, role: MailboxRole) - OptionString { (*role MailboxRole::Inbox self.store.inbox.is_some()).then(|| INBOX.to_string()) }Maildir / m2dir / pimdir无原生角色三个本地后端src/maildir/backend.rs、src/m2dir/backend.rs、src/pimdir/backend.rs构造Mailbox时role一律为None。提案明确把从 Maildir 文件夹名猜测角色列为 Out of scope——猜测不可靠宁可让用户用别名显式声明。别名升级为覆写mailbox.alias.role变更新增的语义是一个以角色命名的别名会覆写后端上报的角色。在 src/account/context.rs 的apply_role_aliases中pub fn apply_role_aliases(self, mailboxes: mut [Mailbox]) { for (key, id) in self.mailbox_alias { let Some(role) MailboxRole::known(key) else { continue; }; if !mailboxes.iter().any(|mailbox| mailbox.id *id) { continue; } for mailbox in mailboxes.iter_mut() { if mailbox.id *id { mailbox.role Some(role.clone()); // 目标邮箱获得该角色 } else if mailbox.role.as_ref() Some(role) { mailbox.role None; // 其他邮箱被摘除该角色 } } } }用途非常明确用户可以用它纠正服务器的错误标注。单元测试role_aliases_override_the_backend_roles演示了典型场景——后端把Sent标成 sent 角色但实际发件箱是 Sent Items配置mailbox.alias.sent Sent Items后Sent Items 获得 sent 角色、原 Sent 失去它。别名键在配置边界统一小写lowercase_alias_keys查询时同样大小写不敏感。配置文件中的完整形态见 config.sample.toml# An entry named after a mailbox role (inbox, sent, drafts, trash, junk, # archive, flagged, important) overrides the role the backend reports. #mailbox.alias.inbox INBOX #mailbox.alias.sent [Gmail]/Sent Mail #mailbox.alias.drafts [Gmail]/Drafts #mailbox.alias.trash [Gmail]/Trash配置结构在 src/config.rs 的MailboxConfig中定义键名为alias兼容旧拼写aliases可同时存在于顶层与[accounts.name]块账户级条目覆盖同名全局条目。-m/--mailbox的三级解析链对 JMAP、Gmail、Graph 这三个会产生不透明 id 的后端src/email/mailbox.rs 引入了缓存化的MailboxIndex其resolve方法实现了完整的解析顺序id 直通输入本身是已知 id原样返回名称匹配按name找到邮箱返回其 id角色匹配输入是已知角色名MailboxRole::known返回携带该角色邮箱的 id兜底透传以上都不匹配原样返回输入让后端以自己的原生名称处理或报错。pub fn resolve(self, mailbox: str) - ResultString { if self.0.iter().any(|m| m.id mailbox) { return Ok(mailbox.to_string()); } if let Some(m) self.0.iter().find(|m| m.name mailbox) { return Ok(m.id.clone()); } if let Some(role) MailboxRole::known(mailbox) let Some(id) self.with_role(role)? { return Ok(id); } Ok(mailbox.to_string()) }因此-m sent在不配置任何别名的情况下也能工作——只要后端上报了 sent 角色。单元测试index_resolves_ids_then_names_then_roles完整验证了这条链m2直通、Inbox按名称命中m1、sent/SENT按角色命中m2大小写不敏感、drafts/unknown原样透传。角色歧义是硬错误with_role对多个邮箱携带同一角色的处理是显式报错而非静默选一src/email/mailbox.rsbail!( Several mailboxes carry the {role} role ({}, {}): pass the mailbox id, or set mailbox.alias.{role} in your configuration, first.id, others.join(, ) );测试index_rejects_a_role_several_mailboxes_carry用两个都标 trash 的邮箱m3、m4验证了错误信息会同时点名两个 id。错误提示给出了两条出路直接传邮箱 id或用mailbox.alias.trash别名消除歧义。省略-m时回退到 inbox 角色这是本变更对用户体验最重要的一处改进。共享命令的-m/--mailbox参数由 src/shared/mailbox/arg.rs 定义其默认值解析为pub fn resolve_mailbox_or_default(account: Account, name: Optionstr) - String { let name name.unwrap_or(MailboxRole::Inbox.as_str()); account.resolve_mailbox(name).to_string() }省略-m时先看mailbox.alias.inbox别名没有则回退到inbox角色由客户端映射到后端自己的收件箱。这消除了 JMAP、Gmail、Graph、IMAP 上缺少 inbox 别名就报错的旧行为——本地化名称如法语的收件箱叫 Réception不再需要用户手工配置别名才能定位收件箱。整个解析链在 src/shared/client.rs 的resolve_mailbox_id中分发JMAP/Gmail/Graph 走各自的MailboxIndex其余后端按原样透传对它们而言名称就是 id。MailboxIndex在客户端内惰性缓存首次解析时拉取一次list_mailboxes如 src/jmap/client.rs 与 src/gmail/client.rs 的mailbox_index()/label_index()之后复用避免每条命令重复全量拉取。角色在命令层的关键消费点message delete垃圾箱来源src/shared/message/delete.rs 演示了角色的实际消费删除消息时先移入垃圾箱垃圾箱的确定顺序是别名优先、后端 trash 角色兜底let trash match account.mailbox_alias.get(trash) { Some(trash) trash.clone(), None client.role_mailbox_id(MailboxRole::Trash)?.ok_or_else(|| { anyhow!(Cannot determine the trash mailbox; set mailbox.alias.trash in your config) }), };即配置的mailbox.alias.trash胜过后端上报的 trash 角色两者都没有时命令报错并指名需要配置的别名键。发送保存--save与message.send.save-copysrc/account/context.rs 的resolve_save中message.send.save-copy true等价于sent角色MailboxRole::Sent.as_str()随后由解析链把sent角色映射到后端真实邮箱它也可以直接写邮箱名称、别名或角色字符串。mailbox list的 ROLE 列src/shared/mailbox/list.rs 在表格中新增 ROLE 列ID | NAME | ROLE--counts时追加TOTAL | UNREAD无角色的邮箱该列为空let mut header vec![Cell::new(ID), Cell::new(NAME), Cell::new(ROLE)];列颜色可通过mailbox.list.table.role-color配置默认 reset。JSON 输出中role直接随Mailbox序列化值为小写拼写。向导不再预填邮箱别名由于后端已在运行时上报角色向导原先把已知原生角色镜像成别名的行为变得多余。按 cairn/spec/wizard.md 的新要求 No mailbox alias pre-fillThe wizard SHALL NOT pre-fillmailbox.alias.*: the backends report their mailbox roles at runtime, and aliases are the users overrides.即向导生成的配置不再写入任何mailbox.alias.*别名完全成为用户的手动覆写手段。规范层面的连带更新还包括cairn/spec/backends.md 新增Mailbox role能力、cairn/spec/config.md 的Mailbox aliases要求改为解析角色且不再要求inbox、cairn/spec/provider-quirks.md 的IMAP special-use is inbox-only for now改为以角色语言表述。已知边界与后续演进IMAP 角色止步于 INBOX其余角色依赖 duesee/imap-codec 的 LIST-EXTENDEDRFC 6154落地后再接入 io-imapio-msgraph 依赖Himalaya 通过 Cargo.toml 的[patch.crates-io]路径条目构建 io-msgraph直到其正式发布本地后端零原生角色Maildir、m2dir、pimdir 只从别名获得角色别名是唯一来源未知角色原样保留MailboxRole::Other不做归一化序列化时保持原始线上拼写如\Custom保证未来协议新增角色不破坏现有解析。小结mailbox-role 变更把按名称猜邮箱升级为按角色定位邮箱共享Mailbox携带可选角色JMAP/Gmail/Graph 从各自原生来源填充IMAP 与 mbox 至少标记收件箱本地后端交给别名mailbox.alias.role成为用户纠正服务器标注的覆写手段-m按 id → 名称 → 角色的顺序解析、省略时回退 inbox 角色。对 UI 层himalaya-tui与脚本调用而言这意味着-m sent、-m trash这类命令在 JMAP 不透明 id 与本地化名称环境下都能稳定命中目标邮箱且不再依赖向导预填的别名。赞分享CLI【免费下载链接】himalayaCLI to manage emails项目地址https://gitcode.com/gh_mirrors/hi/himalaya点击查看免费下载相关推荐himalaya 邮箱角色Mailbox Role全面解析让 UI 与 CLI 摆脱邮箱名称猜测himalaya 邮箱角色Mailbox Role全面解析让 UI 与 CLI 摆脱邮箱名称猜测 导读 本文以 himalaya 项目的 mailboxCLI深入解析 himalaya 的邮箱角色机制共享 Mailbox 模型、后端映射与别名覆盖深入解析 himalaya 的邮箱角色机制共享 Mailbox 模型、后端映射与别名覆盖 himalaya 在 mailbox role 变更中为共享 APICLIhimalaya 邮箱角色Mailbox Role解析从协议原生角色到共享 API 的完整实现指南himalaya 邮箱角色Mailbox Role解析从协议原生角色到共享 API 的完整实现指南 导读 本文基于 himalaya 仓库中已落地 stCLI上一篇终极Ladon开发指南如何为强大内网渗透工具贡献代码下一篇antigravity-skill-orchestrator 技能编排器Agentic Awesome Skills 中的元技能与多领域任务编排实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑