@bumail/jmap

中文文档
0.12.0发布于 2026/11/05MITTypeScriptES Module零依赖近 30 天下载 3439

一个在 alxia 上的 JMAP 服务器( RFC 8620, RFC 8621) : 会话, 包含回溯引用的 API, 邮箱, 电子邮件和线索, blob 下载和上传, 服务任意的@ bumail/ store

此软件包已被标记为弃用

moved to @softistx/bumail-jmap (private, GitHub Packages)

中文使用文档
文档目录11 节
  1. 装入Alxia应用软件
  2. 认证
  3. 它的答案是什么
  4. 上传和下载
  5. 限制
  6. OpenAPI 文档
  7. 陷阱
  8. 推开
  9. API 密码
  10. 文档
  11. 许可证

@ bumail/ jmap (帮助)

Bun 的 JMAP 服务器: JMAP 核心 (RFC 8620) 和 JMAP 邮件 (RFC 8621),作为一个 阿尔克夏 应用 你骑在自己的。 它服务于任何邮件 @bumail/store MailStore - 同一个商店 @bumail/imap 服务,并阅读信件 @bumail/mime: 会话、 API 与 回历、邮箱、电子邮件和线索, queryChanges,blob下载和上传,以及 事件源推 (RFC 8620§7.3).

bun add @bumail/jmap @alxia/core @bumail/store @bumail/mime

@alxia/core (0.19.0或以后,0.20下), @bumail/store 财务报告和审定财务报表 @bumail/mime 为: 端口: 安装您的应用程序使用的版本 . typescript 是可选的 鼠标,用于类型.

装入Alxia应用软件

jmap(options) 返回 alxia 应用程序。 随你上 plugin: 它添加 GET /.well-known/jmap (届会)和 basePath (单位:千美元)/jmap 以 默认), POST /jmap/api, (中文(简体) ), GET /jmap/download/:accountId/:blobId/:name 财务报告和审定财务报表 POST /jmap/upload/:accountId。其每条路线都认证 ; 您的路线被单独处理.

import { alxia } from '@alxia/core';
import { jmap } from '@bumail/jmap';
import { MemoryMailStore } from '@bumail/store';

const store = new MemoryMailStore();
const alice = await store.createAccount('[email protected]');
await store.createMailbox(alice.id, { name: 'INBOX', role: 'inbox' });

const tokens = new Map([['alice-secret-token', alice.id]]);
const passwords = new Map([['alice', await Bun.password.hash('correct horse')]]);

const server = jmap({
    store,
    origin: 'https://mail.example.com', // the public URL the session's URLs start with
    async authenticate(credentials) {
        if (credentials.scheme === 'bearer') return tokens.get(credentials.token) ?? null;
        const hash = passwords.get(credentials.username);
        if (!hash || !(await Bun.password.verify(credentials.password, hash))) return null;
        return alice.id;
    },
});

const app = alxia()
    .get('/health', ({ reply }) => reply(200, 'ok')) // not authenticated
    .plugin(server);

app.listen(8080);

examples/serve.ts 实值 此示例,可以从克隆中运行 : bun install && bun run build,则 bun packages/jmap/examples/serve.ts,则 curl -H 'Authorization: Bearer alice-secret-token' http://localhost:8080/.well-known/jmap。 。 。 .

认证

authenticate 获得 { scheme: 'basic', username, password } 或者说 { scheme: 'bearer', token },则 Request,则 client数字 : { ip, url },主机应用程序 ctx.ip 财务报告和审定财务报表 originalUrl(ctx) 地址a 登录限制计数 。 它回答 id of the 存储账户以服务,或 null (a) 拒绝(有两项挑战的401项). 它运行在下面 hookTimeout (30 s):投出、悬挂或取名 商店没有的账户是503 onError 被告诉.

Basic仅取自HTTPS. 根据明确要求,法院以 前403页 authenticate 被称为,因为 @bumail/imap 财务报告和审定财务报表 @bumail/smtp 在 TLS 之前拒绝登录。 在结束 TLS 的代理后,声明 主机应用程序上的代理 trustProxy阅读他们说的话:

import { alxia, trustProxy } from '@alxia/core';

const app = alxia({
    proxy: trustProxy({ trusted: ['10.0.0.0/8'], untrusted: 'refuse-all' }),
}).plugin(
    jmap({
        …,
        // the scheme the outermost trusted proxy wrote, never the client's
        secure: (_request, client) => client.url.protocol === 'https:',
        // your own check, its limiter keyed by the forwarded client's address
        authenticate: (credentials, _request, client) => check(credentials, client.ip),
    }),
);

allowInsecureBasic: true 取消拒绝,仅供当地检验。 守护者 任何请求都带有标记.

它的答案是什么

方法
Core/echo其论点
Mailbox/get, (中文(简体) ), Mailbox/changes所有财产; myRights 全部捐赠,但 maySubmit,这与 submission 选项
Mailbox/query过滤器通过 parentId, (中文(简体) ), role, (中文(简体) ), name, (中文(简体) ), hasAnyRole, (中文(简体) ), isSubscribed; 排序为 sortOrder 财务报告和审定财务报表 name
Mailbox/queryChanges过滤器和排序 Mailbox/query,与 sinceQueryState, (中文(简体) ), maxChanges, (中文(简体) ), upToId, (中文(简体) ), calculateTotal
Mailbox/set创建、更新 name, (中文(简体) ), parentId 财务报告和审定财务报表 isSubscribed,用 onDestroyRemoveEmails
Email/get元数据, headers,每个 header: 窗体和方便头, bodyStructure, (中文(简体) ), textBody, (中文(简体) ), htmlBody, (中文(简体) ), attachments, (中文(简体) ), bodyValues, (中文(简体) ), preview, (中文(简体) ), hasAttachment
Email/query过滤器通过 inMailbox, (中文(简体) ), inMailboxOtherThan, (中文(简体) ), before, (中文(简体) ), after, (中文(简体) ), minSize, (中文(简体) ), maxSize, (中文(简体) ), hasKeyword, (中文(简体) ), notKeyword, (中文(简体) ), text, (中文(简体) ), from, (中文(简体) ), to, (中文(简体) ), cc, (中文(简体) ), bcc, (中文(简体) ), subject, (中文(简体) ), body,有和,或和不;排序 receivedAt, (中文(简体) ), size, (中文(简体) ), from, (中文(简体) ), to, (中文(简体) ), subject· ; collapseThreads, (中文(简体) ), position, (中文(简体) ), anchor, (中文(简体) ), limit, (中文(简体) ), calculateTotal
Email/queryChanges过滤器,排序和 collapseThreads 联 合 国 Email/query,与 sinceQueryState, (中文(简体) ), maxChanges, (中文(简体) ), upToId, (中文(简体) ), calculateTotal· ; removed 可能是超级集
Email/changes自状态起创建、更新和销毁
Email/set更新 keywords 财务报告和审定财务报表 mailboxIds,完整或补丁; 销毁; 从上传创建 blobId
Email/import来自上传blobs的电子邮件
Thread/get最古老的一条线的电子邮件,先通过商店的线程索引
Thread/changes自状态起创建、更新并销毁线索
Identity/get, (中文(简体) ), Identity/changes, (中文(简体) ), Identity/set与 submission 选项和 urn:ietf:params:jmap:submission 内 using:主机目录的地址表示账户发送为,只读; /changes 从当前状态中空出, 从其它状态中空出 cannotCalculateChanges· ; /set 答题 forbidden
EmailSubmission/get, (中文(简体) ), /changes, (中文(简体) ), /query, (中文(简体) ), /queryChanges, (中文(简体) ), /set与 submission 选项:通过主机发送电子邮件 send 钩 (参见 发送· ; submission: { identities, send }),它决定了谁可以发送;用电子邮件制作的信封,如RFC 8621 第7.5节所言,Bcc从不留于所出之处,在钩子运行之前存储的记录,a deliveryStatus 每个受援者, onSuccessUpdateEmail 财务报告和审定财务报表 onSuccessDestroyEmail 答复者 Email/set 在呼叫本身之后; 信封的 DSN 参数(RET, (中文(简体) ), ENVID, (中文(简体) ), NOTIFY, (中文(简体) ), ORCPT, RFC 3461, 已解码为 RFC 8621 §7 的值) 被检查并给予 send,和会议列表 DSN 内 submissionExtensions; 没有延迟发送或撤消( e)cannotUnsend(中文(简体) )

参考文献#ids 与 resultOf, (中文(简体) ), name (单位:千美元) path 与 *(中文(简体) ) 财务报告和审定财务报表 #creationId在一个请求中工作.

上传和下载

// accountId and inboxId come from the session and Mailbox/get; rawMessage is RFC 5322 bytes
const authorization = 'Bearer alice-secret-token';
const upload = await fetch(`https://mail.example.com/jmap/upload/${accountId}`, {
    method: 'POST',
    headers: { authorization, 'content-type': 'message/rfc822' },
    body: rawMessage,
}).then((response) => response.json()); // { accountId, blobId, type, size }

// then: ["Email/import", { accountId, emails: { a: { blobId: upload.blobId, mailboxIds: { [inboxId]: true } } } }, "0"]

下载服务于电子邮件的字节,其一部分(每个部分) blobId 内 Email/get),或上传,与 Range 支持: GET /jmap/download/{accountId}/{blobId}/{name}?accept={type}。 。 。 .

默认上传会活在记忆中. 保持它们恒久,流畅并共享 每个实例,给予 jmap a 一个 UploadStore 从 @bumail/store数字 :

import { SqliteUploadStore } from '@bumail/store/sqlite';

jmap({ store, origin, authenticate, uploads: SqliteUploadStore.open({ directory: '/var/lib/mail' }) });

内容 ttlMs, (中文(简体) ), maxSize 财务报告和审定财务报表 quotaBytes 然后替换 uploadTtl 财务报告和审定财务报表 uploadQuota。它的ID是随机的,所以同样的字节上传两次是两个 上传,每个上行计算在配额上;内存上传会去dupe.

限制

限制默认
maxSizeRequest10 个 MBaPI 机构: a Content-Length 穿过了它拒绝读取, 一块被割断了的躯体穿过它
maxCallsInRequest16个一个请求中的方法调用
maxConcurrentRequests页:1每个账户飞行中的API请求
maxObjectsInGet, (中文(简体) ), maxObjectsInSet500个编号 a /get; 创建、更新和销毁 /set; 查询的页面
maxSizeUpload25个小米B一个上传,像 maxSizeRequest
maxConcurrentUpload页:1每个账户在飞行中上传
uploadQuota100个米B每个账户的内存上传字节数(与 uploads: 店铺的 quotaBytes(中文(简体) )
uploadTtl3600 s (简体中文)内存上传保存时间(与 uploads: 店铺的 ttlMs(中文(简体) )
maxEventSourceStreams页:1每个账户打开的事件流(429个) limit 通过它)
maxJsonDepth, (中文(简体) ), maxJsonTokens64 100 000美元在解析前检查
maxReferenceItems5000块一个回溯引用扩展为
maxReferenceBytes4个小米BjSON 的字节
maxSizeResponse64个小米B一个API回复的JSON字节
maxQueryScan10 000人电子邮件 1 Email/query 它们的字节、匹配的索引 Email/queryChanges 和电子邮件 a Mailbox/get 计数读取( 过去, 商店的计数)
maxBodyValueBytes1个小米B一个身体值,不管客户问什么
maxBodyValuesTotal16个小米B一个请求中的正体值

把它们放在下面 limits。 。 。 . maxSizeRequest, (中文(简体) ), maxCallsInRequest, (中文(简体) ), maxConcurrentRequests, (中文(简体) ), maxObjectsInGet, (中文(简体) ), maxObjectsInSet, (中文(简体) ), maxSizeUpload 财务报告和审定财务报表 maxConcurrentUpload 将在会话中宣布 urn:ietf:params:jmap:core 能力。 请求打破的限制是 urn:ietf:params:jmap:error:limit 命名问题( 大小为 413) 派去,429个货币,其余400个, maxSizeResponse 包含; 一个被呼叫中断是一个方法 错误。 药是 [A-Za-z0-9_-]{1,255}; 一个账户永远不会到达另一个账户 账户的数据,无论它名称是什么;错误中重复的客户端文本是 在100个字符后切出.

OpenAPI 文档

包装了它所添加路线的OpenAPI 3.1文件,因为 @bumail/jmap/openapi.json (单位:千美元)openapi/jmap.json 在软件包中:

import document from '@bumail/jmap/openapi.json' with { type: 'json' };

document.paths['/api'].post.operationId; // 'api'

它涵盖会议及其会议反对, POST {basePath}/api 与 请求和答复信封,下载(URI模板) 参数, Range 和上传的《基本》和《坚忍》 rFC 8620 问题 (单位:千美元)notJSON, (中文(简体) ), notRequest, (中文(简体) ), unknownCapability, (中文(简体) ), limit,作为 application/problem+json。API、下载和上传服务器 谁 basePath 变量默认值为 /jmap: 与另一个 basePath设置 变量;路径保持不变.

一个方法调用是 [name, arguments, callId],它的论点是 通用描述, 如 RFC 8620 框框: 命名为 #name 是一种从早先的回复中解开的回溯参照,以及一种方法,即 无法回答 error 在200码内引用 绝对不是HTTP状态. RFC 8620和RFC 8621定义了每种方法的论据. 该文件是 文档和合同:服务器不对其进行验证, 当一个路由和文档有分歧时,包的 spect 失败. 内容 info.version 是文档的版本,而不是软件包的版本.

陷阱

  • 搜索存在前所存储的电子邮件在第一次使用时被索引. Email/query 由商店的回答 queryMessages,从搜索 字段中添加电子邮件Email/import 财务报告和审定财务报表 Email/set 传过来. 没有他们的电子邮件最多从其字节读取 maxQueryScan 和田地setSearchFields,没有客户看到; 电话是 serverUnavailable 留下一部分,每一部分 指数增加(绝不是部分结果). Email/queryChanges 拿着 整个结果和答案 requestTooLarge 过去 maxQueryScan 火柴。 比赛. Thread/get 财务报告和审定财务报表 Thread/changes 不扫描: 商店回答它们 . Mailbox/get 计数 unreadEmails, (中文(简体) ), totalThreads 财务报告和审定财务报表 unreadThreads 如RFC 8621 §2所问($seen 在任何拼写读取, $draft 在同一个预算范围内,它的所有邮箱 合计(properties 被弃绝,被弃绝,被弃绝,被弃绝 邮箱的计数是商店的IMAP计数, unseen 对于两个未读 计数和 messages 页:1 totalThreads,这算不上电子邮件 线程,因为商店没有每个邮箱线程数.
  • 上传为内存 页:1 uploadTtl,在此过程中 他们,除非 uploads 给出一个 UploadStore:重启会忘记它们,而且 两个进程没有共享.
  • sortOrder 总是0和邮箱的 role 无法更改: 商店里没有.
  • ifInState 检查,然后调用: 介于 两人没被抓.
  • 推是事件源 (RFC 8620 第7.3节):无WebSocket(RFC 8887), 无按键订阅(§7.2)。 溪流逐一地看店铺 pushInterval 何时 notify 他们被称作;他们生活在一个 因此,在几个案例中,只有民意调查才涉及客户,而客户 更改另一个实例.
  • 会议在 /.well-known/jmap 上面的应用程序数字 : 挂载 jmap() 在一个没有前缀的应用程序上,或者那个路径也会移动.
  • asRaw 显示页眉值 失去他们的领先空间.

推开

GET /jmap/eventsource (届会) eventSourceUrl) 溪流 a StateChange 当一个账户的电子邮件,邮箱,线索,电子邮件交付, 身份或电子邮件 状态动作。 告诉服务器它没有做出修改, 并设置多久一次 寻找其他的pushInterval,以秒计,默认为10:

const server = jmap({ store, origin, authenticate, pushInterval: 5 });
// after a delivery into the store, or an IMAP change:
server.notify(accountId, { delivery: true });
// serving through your own Bun.serve? end the streams before you stop it:
server.close();

API 密码

导出
jmap(options)服务器:要挂载的 alxia 应用程序 plugin,与 notify(accountId, change?) 财务报告和审定财务报表 close(); 丢出一个 JmapError (单位:千美元)INVALID_OPTION) 在一个糟糕的选择
JmapServernotify(accountId, { delivery? }):唤醒账户事件流查看其状态; delivery: true 并移动电子邮件传送; close(): 结束每个已打开的事件流并拒绝新的
JmapOptionsstore, (中文(简体) ), authenticate, (中文(简体) ), origin, (中文(简体) ), basePath, (中文(简体) ), limits, (中文(简体) ), pushInterval, (中文(简体) ), hookTimeout, (中文(简体) ), allowInsecureBasic, (中文(简体) ), secure, (中文(简体) ), onError, (中文(简体) ), submission
JmapSubmission, (中文(简体) ), IdentityInfo页:1 submission 选项 : identities(accountId) 请回答 { email, name } 一个账户发出如下:, send(request) 发出一个信息,并决定谁可以, maxRecipients (默认100);设置它宣布 urn:ietf:params:jmap:submission
SendRequest, (中文(简体) ), SendEnvelope (与 ret, (中文(简体) ), envid, (中文(简体) ), recipients, (中文(简体) ), SendRecipient, (中文(简体) ), SendNotify, (中文(简体) ), SendAnswer, (中文(简体) ), SendRefusal, (中文(简体) ), SendRefusalType, (中文(简体) ), SendDelivery什麽 send 给出和回答
JmapLimits以上限额
JmapCredentials什麽 authenticate 获取 : { scheme: 'basic', username, password } 或者说 { scheme: 'bearer', token }
AuthResult什麽 authenticate 答案:账户编号,或 null 编号 : undefined
JmapClient请求来自谁 authenticate, (中文(简体) ), secure 财务报告和审定财务报表 onError 被告知: ip (单位:千美元)ctx.ip和 url (单位:千美元)originalUrl(ctx)(中文(简体) )
ErrorContext什麽 onError 获取到错误旁边 : request, (中文(简体) ), client, (中文(简体) ), accountId, (中文(简体) ), method
JmapError, (中文(简体) ), JmapErrorCodecode数字 : INVALID_OPTION,以及 HOOK_TIMEOUT,哪个 onError 钩子时得到( w)authenticate, (中文(简体) ), submission.identities, (中文(简体) ), submission.send(b) 未在其中结算 hookTimeout
@bumail/jmap/openapi.json作为 JSON 路径的 OpenAPI 3.1 文件

文档

  • 指数:各页,何时读出.
  • 指南: 挂起、 认证、 会话、 每一种方法、 以及存储的映射方式, 发送(EmailSubmission 编辑 send hook),blobs,限制,OpenAPI文档,RFCs也随之而来.
  • 解决问题: 每一个错误,问题和方法错误,按其准确的文字.
  • 路线图:接下来会发生什么——搜索片段,真空响应——和商店漏洞.

许可证

麻省理工学院

版本与发布记录

@bumail/jmap 共发布 21 个版本,首次发布于 2026/10/04;过去 12 个月发布了 21 个版本。

版本发布日期距今
0.12.0latest2026/11/053 天前
0.11.12026/11/054 天前
0.11.02026/11/054 天前
0.10.42026/11/044 天前
0.10.32026/11/045 天前
0.10.22026/11/045 天前

关于 @bumail/jmap 的常见问题

@bumail/jmap 是什么?

一个在 alxia 上的 JMAP 服务器( RFC 8620, RFC 8621) : 会话, 包含回溯引用的 API, 邮箱, 电子邮件和线索, blob 下载和上传, 服务任意的@ bumail/ store注意:该包已被作者标记为弃用——moved to @softistx/bumail-jmap (private, GitHub Packages)

如何安装 @bumail/jmap?

在项目目录运行 npm install @bumail/jmap,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@alxia/core(^0.19.0)、@bumail/mime(^0.4.0)、@bumail/store(^0.9.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bumail/jmap 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bumail/jmap 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@bumail/jmap 可以商用吗?

@bumail/jmap 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bumail/jmap 的下载量有多少?

根据 npm 官方下载统计,@bumail/jmap 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,439 次,日均约 115 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bumail/jmap 还在维护吗?

最新版本 0.12.0 发布于 2026/11/05(3 天前);至今共发布 21 个版本,其中过去 12 个月发布了 21 个;首次发布于 2026/10/04。作者已将其标记为弃用,新项目建议寻找替代方案。