@bernierllc/ nevarmail (英语)
NevarMail v1 API的官方客户端.
此所住之处. 此软件包发布于
bernierllc/tools(单位:千美元)packages/core/nevarmail),是客户端唯一的真相来源. 它所包裹的 NevarMail API 构建于bernierllc/email_demo。 。 。 . 任何更改 NevarMail v1 API 表面需要匹配的更改, 在同一工作单位中 —— 说 看那个雷波的CLAUDE.md- "NevarMail SDK(英语:@bernierllc/nevarmail) (中文(简体) ).
一个规则: 每一次非-2xx投出. 如果回电,API接受. 没有
"成功"物体内含故障 无法平息出错——审计
生产这个包 发现一个生产集成 吞噬每一个错误,所以
org可能已经邮寄到 403 几个月来,两边都没发现.
安装
npm install @bernierllc/nevarmail
仅服务器侧; 需要节点 20+( 使用全局) fetch 财务报告和审定财务报表 crypto.randomUUID) (中文(简体) ). 永远不要在浏览器代码中披露API证书. 零从属.
融合状况
| 一体化 | 状态 | 为什么 |
|---|---|---|
锁定器( L)@bernierllc/logger(中文(简体) ) | 不适用 | 零依赖性 API 客户端保持零依赖性. 每一次失败都像掷出 NevarMailError 携带 status, (中文(简体) ), code, (中文(简体) ), requestId 财务报告和审定财务报表 cause ——登录在消费者身上,加上消费者的伐木机. |
Never Hub (没有@bernierllc/neverhub-adapter(中文(简体) ) | 不适用 | 核心级软件包没有运行时间生命周期可以注册; NeverHub 注册属于构成此客户端的服务或套件. 因此,此处不需要严重退化. |
使用量
import { NevarMail } from "@bernierllc/nevarmail";
const nevarmail = new NevarMail({ apiKey: process.env.NEVARMAIL_API_KEY! });
const result = await nevarmail.sendEmail({
to: "[email protected]",
subject: "Your quote is ready",
html: "<p>Here it is.</p>",
use_case_id: process.env.NEVARMAIL_USE_CASE_TRANSACTIONAL,
});
console.log(result.id, result.status);
租赁——一键是一角
NevarMail API 密钥 实值 该组织。 任何请求机构都不存在Org id 服务器从密钥中解析并忽略您发送的任何东西。 有两个后果:
- 每人一把钥匙 键不能被指向第二个键 和一个多租户应用程序 代表 NevarMail orgs 发送若干键.
- 永远不要接受来自终端用户的Org标识符并经过. 无处可去 说吧,尝试是一个征兆 融合是建模租借错误.
键、表面和模式
钥匙看起来像 nvm_api_live_…, (中文(简体) ), nvm_api_test_…, (中文(简体) ), nvm_mcp_live_…, (中文(简体) ), nvm_mcp_test_…。 。 。 .
- 表面 (单位:千美元)
api对mcp)——这个包裹会说REST,所以它需要一个api键。 通过一个mcp键和构造器会立即扔出,而不是让服务器回答403 KEY_TYPE_MISMATCH在您的第一个发送, 它读起来像一个认证错误. - 模式 (单位:千美元)
live对test)——测试密钥只发送到org测试上的地址 允许列表; 其他任何东西都是422 TEST_MODE_RECIPIENT_NOT_ALLOWED模式为 属性,绝不是客户端选项:client.mode报告关键是什么.
const nevarmail = new NevarMail({ apiKey: key });
if (nevarmail.mode === "test") console.warn("test key — sends are allow-list only");
错误
每一次失败都是 NevarMailError数字 :
import { NevarMail, NevarMailError } from "@bernierllc/nevarmail";
try {
await nevarmail.sendEmail({ to, subject, html });
} catch (err) {
if (err instanceof NevarMailError && err.code === "RECIPIENT_SUPPRESSED") {
// Expected: this person opted out. Not an outage.
return;
}
throw err; // everything else is worth waking someone for
}
| 外地 | 含义 |
|---|---|
status | HTTP 状态 ; 0 当请求从未到达 API 时 |
code | 机器可读代码——这个分支,永远不要打开 message |
requestId | 引用支持; 匹配 API x-request-id |
retryAfter | 秒,从 Retry-After 在429号公路上 等待30多或更短的时间会为你重试;一个更长的时间(每天一顶帽子)会同时抛出 |
body | 解析的回复, 对于比您安装的版本更新的代码 |
值得明确处理的代码:
| 代码 | 状态 | 这是什么意思 |
|---|---|---|
RECIPIENT_SUPPRESSED | 422个 | 被弹出,被抱怨,或未认购. 不要重试. |
COMPLIANCE_ADDRESS_REQUIRED | 422个 | emailType: "marketing" (或用例收件人可以选择退出)从一个没有邮政地址的项目. 设定为 setProjectAddress。 。 。 . |
TEST_MODE_RECIPIENT_NOT_ALLOWED | 422个 | 测试密钥, 收件人不在允许列表中 . |
CC_BCC_NOT_SUPPORTED_FOR_TEMPLATE | 400个 | 按收件人制作模板邮件; 发送一个请求 . |
VALIDATION_ERROR | 400个 | 身体错. 复试无益. |
SCOPE_DENIED | 403 (韩语) | 密钥缺乏这条路线需要的范围. |
KEY_TYPE_MISMATCH | 403 (韩语) | 在 REST 路径上的 MCP 密钥( 此客户端在构建时拒绝) . |
RATE_LIMITED | 429个 | 自动复试;预算用完后浮出水面. |
SCHEDULE_NOT_CANCELLABLE | 409个 | 预定发送已发射或已取消 . |
NETWORK_ERROR | 0 个 | 完全没有反应——DNS,TLS,或超时. |
检索
复试: 429 (荣誉) Retry-After, (中文(简体) ), 5xx,和网络故障——指数倒置
与 jitter , 默认的两次额外尝试(maxRetries) (中文(简体) ).
从未再审:任何其他 4xx它会永远失败 所以重试会变成一个坏的
请求为三。 发送不是同位素 5xx 可能交付两次.
案件使用和制止范围
每个项目都有禁忌. checkSuppression(email, useCaseId?, projectId?) 答案为
一个项目: projectId,否则项目 useCaseId,否则组织的唯一
项目。 一个有多个项目的组织 400 PROJECT_REQUIRED 当两者都不是
通过了。 对供应商账户的回弹或垃圾邮件投诉会阻碍每个项目;任何其他
镇压只能阻止它自己.
use_case_id 命名信件类别。 这是区别于
"这个人不订阅你的通讯"和"这个人不能再被电子邮件":
- 说吧,并选择退出 不同 类别列表不阻碍发送, 并且标记为不可压制的类别可免于选择不适用.
- 别管了,并且对该项目的每一个积极抑制都适用——严格解读.
仅a 项目范围密钥 可能会漏掉
sendEmail编号 :scheduleEmail: 发送为 那个项目的默认使用大小写。 一个 粗略键 省略它会得到422 USE_CASE_REQUIRED· ;listUseCases()显示你的.
弹跳、垃圾邮件投诉和手动压制 阻止了每个类别 仅a
use_case_id 您通过自身可以缩小一个块;默认服务器解析不能.
sendEmail 也用"弹"来表示 useCase: "receipts" - 而不是其编号(其中之一
两个,不是两个。 弹簧必须存在于键可以打开的完全一个工程中:未知是一个
404, 几个项目中的同一颗子弹是: 400 USE_CASE_AMBIGUOUS (通过) use_case_id ) (中文(简体) ).
listUseCases 显示弹壳.
方法
| 方法 | 路线 | 范围 |
|---|---|---|
sendEmail | POST /api/v1/email/send | email:send |
sendEmailToList | POST /api/v1/email/send-to-list | email:send |
scheduleEmail | POST /api/v1/email/schedule | email:send |
listScheduledEmails | GET /api/v1/email/schedule | email:send |
cancelScheduledEmail | DELETE /api/v1/email/schedule/{id} | email:send |
checkSuppression | GET /api/v1/suppressions/check | org:read |
getPreferences(email, projectId?) - 一个项目中的一个地址名单; projectId 当鼠标有多个时需要 | GET /api/v1/preferences | contacts:read |
setPreferences(email, { subscribedListIds, unsubscribeAll, projectId? }) ——保留完整的一组名单 | PUT /api/v1/preferences | contacts:write |
listTemplates 编号 : createTemplate 编号 : getTemplate 编号 : updateTemplate 编号 : deleteTemplate | /api/v1/templates | templates:read 编号 : templates:write |
renderTemplate | POST /api/v1/templates/{id}/render | templates:read |
listCampaigns({ projectId?, page?, perPage? }) 编号 : getCampaign(id, { projectId? }) ——只读 | GET /api/v1/campaigns, (中文(简体) ), GET /api/v1/campaigns/{id} | campaigns:read |
getCampaignAnalytics(id, { projectId? }) ——计数加交付/打开/点击/弹出/不认购率(0比1) | GET /api/v1/campaigns/{id}/analytics | analytics:read |
getAnalyticsSummary({ period? } | { start, end }) - 总体计算和跟踪率 7d编号 :30d编号 :90d (默认) 30d)或包容性世界协调时 YYYY-MM-DD 范围 | GET /api/v1/analytics/summary | analytics:read |
getUsage({ start, end }) ——用于包容性世界协调世界时的计量吸入器外送电子邮件计数(可填单和测试模式分开) YYYY-MM-DD 最多366天; 仅计数,不计费用 | GET /api/v1/usage | usage:read |
listLists 编号 : createList 编号 : getList | /api/v1/lists | contacts:read 编号 : contacts:write |
addListContacts | POST /api/v1/lists/{id}/contacts | contacts:write |
removeListContact | DELETE /api/v1/lists/{id}/contacts?email= (只列出一个清单;保持联系) | contacts:write |
listContacts 编号 : upsertContacts 编号 : updateContact 编号 : deleteContact | /api/v1/contacts | contacts:read 编号 : contacts:write |
listBusinesses({ projectId?, page?, perPage? }) 编号 : getBusiness(id, { projectId? }) | GET /api/v1/businesses, (中文(简体) ), GET /api/v1/businesses/{id} | contacts:read |
createBusiness(input) 编号 : updateBusiness(id, input) 编号 : deleteBusiness(id, { projectId? }) - 更新替换 domains合并 attributes; 删除保留联系人 | POST /api/v1/businesses, (中文(简体) ), PATCH 编号 : DELETE /api/v1/businesses/{id} | contacts:write |
upsertBusinessByExternalRef(system, ref, input) ——同位素;返回 { business, created } | PUT /api/v1/businesses/by-ref/{system}/{ref} | contacts:write |
listBusinessMembers(id, { projectId?, page?, perPage? }) | GET /api/v1/businesses/{id}/members | contacts:read |
addBusinessMembers(id, members, { addedBy?, projectId? }) (最多100个)/ removeBusinessMembers(id, { contactIds?, emails?, projectId? }) 仅限成员 | POST 编号 : DELETE /api/v1/businesses/{id}/members | contacts:write |
addBusinessMembersByDomain(id, { projectId? }) - 在企业领域增加联系人 | POST /api/v1/businesses/{id}/members/by-domain | contacts:write |
listSenders 编号 : createSender(input) —— 说 input.projectId? 当鼠标有多个时, 目标为项目 | /api/v1/senders | senders:read 编号 : senders:write |
getSender 编号 : updateSender 编号 : deleteSender | /api/v1/senders/{id} | senders:read 编号 : senders:write |
listUseCases({ projectId?, includeArchived? }) 编号 : createUseCase(input) —— 说 input.projectId? 关于 createSender | /api/v1/use-cases | use-cases:read 编号 : use-cases:write |
getUseCase 编号 : updateUseCase 编号 : archiveUseCase 编号 : restoreUseCase ——删除档案;恢复撤销 | /api/v1/use-cases/{id} | use-cases:read 编号 : use-cases:write |
importSenders({ projectId? }) ——采用项目供应商账户身份;再运行以获取供应商核查 | POST /api/v1/senders/import | senders:write |
listDomains 编号 : createDomain(domain, { projectId }) 编号 : getDomain 编号 : deleteDomain | /api/v1/domains | domains:read 编号 : domains:write |
getDomainDnsRecords 编号 : verifyDomain | /api/v1/domains/{id}/… | domains:read 编号 : domains:write |
getDomainBrandedTracking 编号 : enableDomainBrandedTracking | GET 编号 : POST /api/v1/domains/{id}/branded-tracking | domains:read 编号 : domains:write |
listInboxes 编号 : getInbox 编号 : updateInbox(id, { displayName?, forwardTo?, storeMessages?, openTracking?, status? }) —— 说 openTracking: true 需要一个活跃的 HTTPS 品牌主机 | /api/v1/inboxes, (中文(简体) ), /api/v1/inboxes/{id} | inboxes:read 编号 : inboxes:write |
listSuppressions 编号 : addSuppressions | /api/v1/suppressions | org:read 编号 : org:write |
getProjectAddress(projectId) 编号 : setProjectAddress(projectId, { physicalAddress, addressComponents? }) | GET 编号 : PUT /api/v1/projects/{id}/address | org:read 编号 : org:write |
getInboxDbStatus | GET /api/v1/inbox-db/status | org:read |
connectInboxDb({ connectionString, provider? }) (202,启动背景连接) / testInboxDbConnection | POST /api/v1/inbox-db/connection 编号 : …/connection/test | org:write |
getInboxDbConnectAttempt (投弹直到 status 没有 running(中文(简体) ) | GET /api/v1/inbox-db/connection | org:read |
应用程序级别的细节, 在您击中它们之前需要知道 :
- 列表方法保持pagination.
listTemplates和朋友回来{ data, pagination: { page, perPage, total } }- 信封的标注是 调用列表路由, 因此它不会被取消 。 单资源方法返回有效载荷 直接,与发送方法相同. - 企业.
client.businesses组合十种商业方法:list, (中文(简体) ),get, (中文(简体) ),create, (中文(简体) ),update, (中文(简体) ),delete, (中文(简体) ),upsertByExternalRef, (中文(简体) ),addByDomain财务报告和审定财务报表members.{list, add, remove},每个 授权给上面的平面方法。 租赁:projectId查询中get, (中文(简体) ),delete财务报告和审定财务报表addByDomain,在体内为update, (中文(简体) ),createBusiness而会员写作。 别管了 仅当 org 有一个项目时. - 更新为
PATCH和部分. 只发送您正在更改的字段 . - 运动读取选择
projectId。 。 。 . 只有在雄兽有一个项目时才开放; 否则服务器会回答 400PROJECT_REQUIRED. 另一个项目中的运动是404NOT_FOUND和失踪的一样 无工程的Org得到422NO_PROJECT; 无 销售商得到404NO_MARKETING_PROVIDER。 。 。 .listCampaigns在提供者的页面后过滤, 所以是一个页面 可持有少于perPage作行相会. 分析路径需要analytics:read没有campaigns:read。 。 。 . renderTemplate(id, variables)将变量映射为整个正文 - 不包着 (单位:千美元)variables键.deleteTemplate从来没有404s: 未知的 id 解析{ deleted: false }两百块 每个 其他删除将丢入缺失的资源.- 列表没有更新或删除 ——API不曝光;SDK不发明.
- 禁止范围是跨战地的数字 :
scope: "use_case"需求use_case_id,以及use_case_id与scope: "account"是一个400。 。 。 .projectId命名工程所在区域 输入;省略后,它们会降落在使用项目中,否则每个项目中钥匙的所有人都可以打开. - 管理中的账户无法删除域 —— 说
deleteDomain掷出403 MANAGED_ACCOUNT。 。 。 . - 收件箱- db 连接字符串发送一次, 从未重复 .
connectInboxDb存储加密;InboxDbStatus省却.schemaName您数据库中的Postgres schema NevarMail 拥有 .
任何尚未覆盖的东西——一个网点——都经过
request() (或 requestPaginated() 用于列表路由),它应用相同的认证,信封
打开、丢弃和重试 :
const endpoints = await nevarmail.request<unknown>("GET", "/api/v1/webhook-endpoints");
到达 request() 而不是写第二个 HTTP 客户端 。 就是这样
第一次发生了吞咽-过敏症.
sendEmailToList 返回卷起而非判决
发送到一些收件人的列表是 200 与 status: "partial" 和一个非零
failedCount。检查字段;2xx并不意味着每个人都收到.
const run = await nevarmail.sendEmailToList({ listId, subject, htmlContent });
if (run.status !== "sent") console.error(run.failedCount, run.failures);
日程安排需要实时
scheduled_for 必须带有明确的抵消—— 2026-09-01T14:00:00Z 或者说
2026-09-01T10:00:00-04:00赤裸的当地时间是四个不同的时刻,取决于谁
输入,所以API拒绝而不是猜测. 在你的一侧转换区域
其实已经知道。 模板的调度发送被拒绝: 处理器不渲染
模板,所以接受一个会排队一个空体.
代理收件箱 - NevarMailInbox
一个代理收件箱有自己的API(/api/v1/inbox/*)及其本人的证书:客户身份
(单位:千美元)nvm_inbox_…从收件箱的代理标签里找到的秘密 API 密钥在那里被拒绝, 并且 a
收件箱的证书在自己的收件箱之外没有任何内容,所以它是一个单独的客户端:
import { NevarMailInbox } from "@bernierllc/nevarmail";
const inbox = new NevarMailInbox({
clientId: process.env.NEVARMAIL_INBOX_CLIENT_ID!,
clientSecret: process.env.NEVARMAIL_INBOX_CLIENT_SECRET!,
// scopes: ["inbox:read"], // narrow the token; defaults to inbox:read + inbox:send
});
const { data } = await inbox.listEmails({ perPage: 10 });
for (const summary of data.filter((e) => !e.isRead)) {
const email = await inbox.getEmail(summary.id);
await inbox.send({
to: email.from,
subject: `Re: ${email.subject ?? ""}`,
text: "Got it.",
reply_to_email_id: email.id,
});
await inbox.markEmailRead(email.id);
}
| 方法 | 路线 | 范围 |
|---|---|---|
listEmails({ page, perPage, threadId }) | GET /api/v1/inbox/emails | inbox:read |
getEmail(id) | GET /api/v1/inbox/emails/{id} | inbox:read |
markEmailRead(id) | POST /api/v1/inbox/emails/{id}/read | inbox:read |
deleteEmail(id) | DELETE /api/v1/inbox/emails/{id} | inbox:read |
send(input) | POST /api/v1/inbox/send | inbox:send |
rotateCredentials() | POST /api/v1/inbox/credentials/rotate | inbox:read |
- 托肯斯为你处理. 客户将证书交换为短命
符号( OAuth)
client_credentials时间/api/oauth/token在第一次使用, 更新一分钟 期满前,以及401得到一个新鲜的标志 并重复一次。 被拒绝的证书 掷出code: "INVALID_CLIENT"。 。 。 . send是故意缩小. 总是收件箱的地址,最多5个 整个接收者to财务报告和审定财务报表cc; 没有模板或列表。 击中每天的限制是429 inbox_daily_limit_reached{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}马上抛出retryAfter设置到 直到协调世界时午夜.rotateCredentials()返回新密钥一次 并切换客户端到它. 贮之. 老秘密停止运作.
相同的选项( O)baseUrl, (中文(简体) ), timeoutMs, (中文(简体) ), maxRetries, (中文(简体) ), fetch)和与
NevarMail。 。 。 .
选项
new NevarMail({
apiKey: process.env.NEVARMAIL_API_KEY!,
baseUrl: "https://app.nevarmail.com", // default
timeoutMs: 30_000, // default
maxRetries: 2, // default
fetch: undefined, // inject one for tests or a custom runtime
});
使用大小写组和继承的设置
创建可重复使用的项目默认值 createUseCaseGroup,然后创建只使用其名称和组ID的用例。 组合允许可压制的,交易的,或混合使用的案例;混合组合每个类型都有默认的种类和单独的跟踪默认.
const group = await mail.createUseCaseGroup({
name: "Account emails", projectId,
allowedKind: "transactional", defaultSuppressable: false,
defaults: {
shared: { fromName: { value: "Accounts" } },
suppressable: {}, transactional: { clickTracking: { value: false } },
},
});
const login = await mail.createUseCase({ name: "Login", groupId: group.id });
await mail.updateUseCase(login.id, {
expectedRevision: login.settingsRevision,
settings: { set: { replyToEmail: null }, inherit: ["fromName"] },
});
省略的覆盖继承; 明示 false 禁用; 明确 null 清除一个无效的字段. settings 关于答复的内容包括有效/已配置的值、源和覆被状态。 与组值等同的覆被仍为覆被. 集团默认为继承人保留活性.
使用 listUseCaseGroups, (中文(简体) ), getUseCaseGroup,以及 deleteUseCaseGroup 管理团体。 删除返回 {id, deleted: true} 并拒绝包含任何成员的团体,包括存档成员。 要编辑默认,请调用 previewUseCaseGroupUpdate(id, {changes})然后审查受影响的成员,然后 updateUseCaseGroup 这些修改,预览的修改和符号.
const target = { groupId: destinationId }; // null means Ungrouped
const preview = await mail.previewUseCaseGroupMove(login.id, target);
// Show preview.rows and both outcomes to the user before applying.
const idempotencyKey = globalThis.crypto.randomUUID();
const moved = await mail.moveUseCaseToGroup(login.id, {
...target, previewToken: preview.previewToken,
policy: "inherit_destination", // or "preserve_effective"
idempotencyKey,
});
移动保留已有的覆盖 . preserve_effective 附加快照 。 一种变换用品 suppressable 通过相同的预览/应用流,而不是普通的元数据 PATCH. Tokens在15分钟后过期并绑定在演员/镜像上;在预览时使用相同的证书并应用. 一个 stale 预览返回 409, 并且没有重试 : 获取另一个比较并再次获得确认 .
移动键是请求正文中的UUID. 每一次SDK呼叫都会产生一次出局,保存在自动回放中. 在网络结果模糊后重新测试整个呼叫时,保留和再利用一个明确的关键。 承诺的重试即使在符后返回其原始结果;为不同的动作重用密钥是冲突.
生产过渡需要 confirmation: {productionSending: true} 和服务器权限/准备状态。 对交易邮件进行跟踪 transactionalTracking: "transactional-tracking-v1" 在用户看到披露后。 永远不要自动确认这些选择。 跟踪是解决的服务器侧; SendEmailInput 没有追踪功能。 保留 API 证书服务器侧:应用浏览器组件使用会话路由.