{"_id":"anypost","_rev":"6-3e3529d381b8b08c7aed070f10607e9b","name":"anypost","dist-tags":{"latest":"1.4.0"},"versions":{"1.0.0":{"name":"anypost","version":"1.0.0","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"license":"MIT","_id":"anypost@1.0.0","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"homepage":"https://anypost.com","bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"dist":{"shasum":"6771a188ef98656afa654a1deae9cbfd27ce1980","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.0.0.tgz","fileCount":9,"integrity":"sha512-KMK0vWBDKqEPgsQod3V4bExgt6FnlGzD+gR72QQoiko1sb4sheX+c5E3wTvmp05+GrD6kYuJAfJTexij2oD5Dg==","signatures":[{"sig":"MEUCIQC+T65Fe40kGgCAlDASskeNELttAGoE1A5TUyJ4DbHy5AIgSWDrzAeJrxSO7JQMJkbG0RwKNqA4+gXmwZ1mWO3Heyg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":356486},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"8eb04b92629053af52f61040d5c33ef8780d4c99","scripts":{"test":"bun run sync-version && bun test","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","sync-version":"node scripts/sync-version.mjs","prepublishOnly":"bun run typecheck && bun run build"},"_npmUser":{"name":"danhstevens","email":"danhstevens@gmail.com"},"repository":{"url":"git+https://github.com/anypost/anypost-node.git","type":"git"},"_npmVersion":"10.8.2","description":"Official TypeScript SDK for the Anypost email API.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","emailmd":"^0.3.3","@types/bun":"^1.3.14","typescript":"^5.9.3","@types/node":"^25.9.1"},"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/anypost_1.0.0_1780981755899_0.9603246151321132","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"anypost","version":"1.1.0","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"license":"MIT","_id":"anypost@1.1.0","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"homepage":"https://anypost.com","bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"dist":{"shasum":"75a4cd97e62b77ebac9b444a981000b198371363","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.1.0.tgz","fileCount":9,"integrity":"sha512-8N6ZuqMQ2rQO97FP0XlWLpf2F3rNT1YE0eGiKuO2BeN6QT8c2KxPEpv01zZiqfeb0uy2En4Hoff8s8gDAViUXQ==","signatures":[{"sig":"MEUCIQDs1rpr/vaoWeU7raxHE3bvrcuMFCAExbpw5ntxhbCQdQIgOD+fQyRk7nhwg4h6a+6trS40EV6MEhBnXwVZyv7w39I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":358650},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"02e9b81edc1c108f4aad46ab6ba88e3e3d8f152c","scripts":{"test":"bun run sync-version && bun test","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","sync-version":"node scripts/sync-version.mjs","prepublishOnly":"bun run typecheck && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:57319c90-3ae0-49c8-9483-8d5080cc9b39"}},"repository":{"url":"git+https://github.com/anypost/anypost-node.git","type":"git"},"_npmVersion":"11.17.0","description":"Official TypeScript SDK for the Anypost email API.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","emailmd":"^0.3.3","@types/bun":"^1.3.14","typescript":"^5.9.3","@types/node":"^25.9.1"},"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/anypost_1.1.0_1781797829647_0.0016977812937117864","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"anypost","version":"1.1.1","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"license":"MIT","_id":"anypost@1.1.1","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"homepage":"https://anypost.com","bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"dist":{"shasum":"aca3cf74ac46066f8c1d85257659d53adf33c274","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.1.1.tgz","fileCount":9,"integrity":"sha512-mNcF6bAOqLEmgozeO8QdfDkuT5X+wDv8tXmt/EpHnUhtIIpuTi6wZInbEos3y3JBl6NJoc5/tD1RJI4Ghobqxg==","signatures":[{"sig":"MEUCIFwVrihFDmUT8mJKjDSWakysIQ6mQvZjcWdsHpSX5vGUAiEA7+oPd/37GWBAcDu2uDNvVqigIgYshqci5rbHSX90cl8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":357880},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"71336765bdd82695a05f5c4f60ced32e4d2a31ef","scripts":{"test":"bun run sync-version && bun test","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","sync-version":"node scripts/sync-version.mjs","prepublishOnly":"bun run typecheck && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:57319c90-3ae0-49c8-9483-8d5080cc9b39"}},"repository":{"url":"git+https://github.com/anypost/anypost-node.git","type":"git"},"_npmVersion":"11.17.0","description":"Official TypeScript SDK for the Anypost email API.","directories":{},"sideEffects":false,"_nodeVersion":"24.17.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","emailmd":"^0.3.3","@types/bun":"^1.3.14","typescript":"^5.9.3","@types/node":"^25.9.1"},"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/anypost_1.1.1_1782321523227_0.3392187792777539","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"anypost","version":"1.2.0","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"license":"MIT","_id":"anypost@1.2.0","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"homepage":"https://anypost.com","bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"dist":{"shasum":"ed9634dc1854b16bd9b4166e02c872c9a984592e","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.2.0.tgz","fileCount":9,"integrity":"sha512-nkoyc1VMVuVmDshKlS2aYeftHnCx9QToPZ2ydHXmV103megzRH0TGvid86T1VU13B1UdHSlNzlIWG4O1/QF4pA==","signatures":[{"sig":"MEUCIDflAoFuzaU/iQBaMSKu7zVyK5wfb9UoziVlmFUSHe1yAiEAi6jYPF2LanJ62RikZVQ17rXaFYnIbJNHzRWTwl547yw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":361939},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"c09f11b9ce3b69f1164e5b92fd8258712bdfcf1b","scripts":{"test":"bun run sync-version && bun test","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","sync-version":"node scripts/sync-version.mjs","prepublishOnly":"bun run typecheck && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:57319c90-3ae0-49c8-9483-8d5080cc9b39"}},"repository":{"url":"git+https://github.com/anypost/anypost-node.git","type":"git"},"_npmVersion":"12.0.2","description":"Official TypeScript SDK for the Anypost email API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","emailmd":"^0.3.3","@types/bun":"^1.3.14","typescript":"^5.9.3","@types/node":"^25.9.1"},"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/anypost_1.2.0_1785452276726_0.4192528923892478","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"anypost","version":"1.3.0","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"license":"MIT","_id":"anypost@1.3.0","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"homepage":"https://anypost.com","bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"dist":{"shasum":"6eed64689ab26efd265aa325f27f29198e5caae7","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.3.0.tgz","fileCount":9,"integrity":"sha512-vSSR/m21KL2sHWGFVvO7r8UvD3XUT0KSzXoUZEFhCY9Fq+re5tNyQFDK4eOAXrmcROsp+msJ0oQcVldPmgA6kA==","signatures":[{"sig":"MEUCIEKcxDLu5KJ8QPgSa6i/d+tBIJAT9/kFInV+lscJ/xXgAiEA6bd4LDM0PHrO+1jcy8Z3v72s3bL0Yq3oUQxWQLEFh+w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":363863},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"b935a9029261466517bb5c2bcc0b621c1a399f04","scripts":{"test":"bun run sync-version && bun test","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","sync-version":"node scripts/sync-version.mjs","prepublishOnly":"bun run typecheck && bun run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:57319c90-3ae0-49c8-9483-8d5080cc9b39"}},"repository":{"url":"git+https://github.com/anypost/anypost-node.git","type":"git"},"_npmVersion":"12.0.2","description":"Official TypeScript SDK for the Anypost email API.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","emailmd":"^0.3.3","@types/bun":"^1.3.14","typescript":"^5.9.3","@types/node":"^25.9.1"},"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/anypost_1.3.0_1785789913205_0.42815249246500153","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"anypost","version":"1.4.0","description":"Official TypeScript SDK for the Anypost email API.","license":"MIT","homepage":"https://anypost.com","repository":{"type":"git","url":"git+https://github.com/anypost/anypost-node.git"},"bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"keywords":["anypost","email","transactional-email","smtp","api","markdown"],"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"engines":{"node":">=18"},"sideEffects":false,"peerDependencies":{"emailmd":">=0.3.0"},"peerDependenciesMeta":{"emailmd":{"optional":true}},"scripts":{"sync-version":"node scripts/sync-version.mjs","build":"bun run sync-version && tsup","typecheck":"tsc --noEmit","test":"bun run sync-version && bun test","prepublishOnly":"bun run typecheck && bun run build"},"devDependencies":{"@types/bun":"^1.3.14","@types/node":"^25.9.1","emailmd":"^0.3.3","tsup":"^8.5.1","typescript":"^5.9.3"},"gitHead":"cad27e9c1086118bde2f794b58aee59ef779e4bb","_id":"anypost@1.4.0","_nodeVersion":"24.18.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-wHam8T2cj2hOiKRbm6fnSXRpEzX6CF9fQSwDatJzD9bNDKNevDVuVFvOLDg4kzohuOM6/m0WVk6dCToA0dTCcA==","shasum":"f1667eaea0de439e317a575d781ca40d2a33ff1f","tarball":"https://registry.npmjs.org/anypost/-/anypost-1.4.0.tgz","fileCount":9,"unpackedSize":366002,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/anypost@1.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHL8sH+JlM1s82uSZs62474ZD6bimJDN+2OzTQJTcU5qAiBnWxYoTX19oDWr/WmQEuYMNZQVdeHRvoVF+HhwOlc4kg=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:57319c90-3ae0-49c8-9483-8d5080cc9b39"}},"directories":{},"maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/anypost_1.4.0_1785792549034_0.45919315865674015"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-09T05:09:15.801Z","modified":"2026-08-03T21:29:09.516Z","1.0.0":"2026-06-09T05:09:16.068Z","1.1.0":"2026-06-18T15:50:29.794Z","1.1.1":"2026-06-24T17:18:43.366Z","1.2.0":"2026-07-30T22:57:56.884Z","1.3.0":"2026-08-03T20:45:13.356Z","1.4.0":"2026-08-03T21:29:09.207Z"},"bugs":{"url":"https://github.com/anypost/anypost-node/issues"},"license":"MIT","homepage":"https://anypost.com","keywords":["anypost","email","transactional-email","smtp","api","markdown"],"repository":{"type":"git","url":"git+https://github.com/anypost/anypost-node.git"},"description":"Official TypeScript SDK for the Anypost email API.","maintainers":[{"name":"danhstevens","email":"danhstevens@gmail.com"}],"readme":"# Anypost Node SDK\n\nThe official TypeScript and JavaScript client for the [Anypost](https://anypost.com) email API.\n\nRuns on Node 18+, Bun, Deno, and edge runtimes. Zero dependencies.\n\nThis README covers the SDK itself: installation, idioms, and configuration. For platform concepts and the full field-level API reference, see the [Anypost documentation](https://anypost.com/docs).\n\n## Install\n\n```bash\nnpm install anypost\n```\n\n## Quickstart\n\n```ts\nimport { Anypost } from \"anypost\";\n\nconst anypost = new Anypost(\"ap_your_api_key\");\n\nconst { id } = await anypost.email.send({\n  from: \"YourCo <you@yourdomain.com>\",\n  to: [\"you@example.com\"],\n  subject: \"Welcome to Anypost\",\n  html: \"<p>Hello, inbox!</p>\",\n});\n```\n\nThe constructor also reads `ANYPOST_API_KEY` from the environment:\n\n```ts\nconst anypost = new Anypost();\n```\n\nKeep the key server-side. It is a bearer credential; never ship it to a browser or mobile app.\n\n## Sending\n\nOne of `text`, `html`, or `template_id` is required. All recipients in `to`, `cc`, and `bcc` share one envelope and count against a combined limit of 50.\n\n```ts\nawait anypost.email.send({\n  from: \"YourCo <you@yourdomain.com>\",\n  to: [\"a@example.com\", \"b@example.com\"],\n  cc: [\"team@example.com\"],\n  reply_to: \"support@yourdomain.com\",\n  subject: \"Receipt #4823\",\n  html: \"<p>Thanks for your order.</p>\",\n  text: \"Thanks for your order.\",\n  tags: [\"receipt\"],\n});\n```\n\nPass attachment `content` as raw bytes and the client base64-encodes it; pass an already-encoded string and it is sent as-is. The request body is capped at 5 MB.\n\n```ts\nimport { readFileSync } from \"node:fs\";\n\nawait anypost.email.send({\n  from: \"YourCo <you@yourdomain.com>\",\n  to: [\"someone@example.com\"],\n  subject: \"Your report\",\n  text: \"Attached.\",\n  attachments: [\n    { filename: \"report.pdf\", content: readFileSync(\"report.pdf\") },\n  ],\n});\n```\n\nSend with a published template and per-recipient variables:\n\n```ts\nawait anypost.email.send({\n  from: \"YourCo <you@yourdomain.com>\",\n  to: [\"someone@example.com\"],\n  template_id: \"template_018f2c5e-3a40-7a91-9c25-3a0b1d5e6f78\",\n  variables: { name: \"Ada\", plan: \"pro\" },\n});\n```\n\nSee the [send reference](https://anypost.com/docs/reference/emails) for the complete field list.\n\n## Markdown\n\nWrite the body in Markdown and the SDK renders it to email-safe HTML and a plain-text alternative through [`emailmd`](https://www.emailmd.dev). Pass `markdown` in place of `html`/`text`:\n\n```ts\nawait anypost.email.send({\n  from: \"YourCo <you@yourdomain.com>\",\n  to: [\"someone@example.com\"],\n  subject: \"Welcome\",\n  markdown: \"# Welcome\\n\\nThanks for signing up. [Get started](https://app.example.com).\",\n});\n```\n\n`emailmd` is an optional peer dependency. Install it only if you send Markdown:\n\n```bash\nnpm install emailmd\n```\n\nIt requires Node 20+ and does not run in edge or browser runtimes, so the rest of the SDK stays dependency-free and portable. Calling a Markdown path without `emailmd` installed throws a clear error.\n\nPass render options (theme, fonts, validation) through `markdownOptions`, and reach the renderer directly with `renderMarkdown` to render once and reuse the result, or to read the extracted frontmatter:\n\n```ts\nimport { renderMarkdown } from \"anypost\";\n\nconst { html, text, meta } = await renderMarkdown(\"# Hi\", {\n  theme: { brandColor: \"#4f46e5\" },\n});\n// meta.preheader, etc.\n```\n\n`markdown` cannot be combined with `html` or `text`. In a batch, set `markdownOptions` on `defaults` to apply one theme to every entry's render.\n\n## Batch\n\nSend 1 to 100 independent messages in one request. `defaults` fills any field an entry omits.\n\n```ts\nconst result = await anypost.email.sendBatch({\n  defaults: { from: \"YourCo <you@yourdomain.com>\" },\n  emails: [\n    { to: [\"a@example.com\"], subject: \"Hi A\", text: \"...\" },\n    { to: [\"b@example.com\"], subject: \"Hi B\", text: \"...\" },\n  ],\n});\n```\n\nA batch with mixed outcomes returns HTTP `207` and resolves normally. Inspect each entry rather than relying on a thrown error:\n\n```ts\nconsole.log(result.summary); // { total, queued, failed }\n\nfor (const entry of result.data) {\n  if (entry.status === \"queued\") {\n    console.log(entry.index, entry.id);\n  } else if (entry.error.type === \"quota_exceeded\") {\n    // A label cap denied this entry. Narrowing on `type` reaches the denial\n    // detail, so there is no need to parse the message.\n    retryAfter(entry.index, entry.error.scope, entry.error.retry_after_seconds);\n  } else {\n    console.error(entry.index, entry.error.type, entry.error.message);\n  }\n}\n```\n\n## Domains\n\nManage sending domains under `anypost.domains`. Add a domain, publish the DNS records it returns, then verify.\n\n```ts\nconst domain = await anypost.domains.create({ name: \"example.com\" });\n\nfor (const record of domain.dns_records) {\n  console.log(record.type, record.name, \"->\", record.value);\n}\n\nconst checked = await anypost.domains.verify(domain.id);\nif (checked.status !== \"verified\") {\n  // verify resolves with the current domain even while pending; it never throws\n  console.log(checked.verification_failure?.code);\n}\n```\n\n`get`, `update` (tracking config only), and `delete` round out the resource. See [Domains](https://anypost.com/docs/reference/domains) for the verification lifecycle and field reference.\n\n## API keys\n\nManage keys under `anypost.apiKeys`. The plaintext secret comes back only once, on `create`, as `key`, so store it then.\n\n```ts\nconst created = await anypost.apiKeys.create({\n  name: \"Production server\",\n  permissions: \"send_only\",\n  allowed_domains: [\"example.com\"],\n});\nconsole.log(created.key); // never retrievable again\n```\n\n`get` returns metadata only (`key_prefix`, never the secret); `update` and `delete` round out the resource. See [API keys](https://anypost.com/docs/reference/api-keys) for the permission model and cache propagation.\n\n## Templates\n\nTemplates use a draft/published model: edits land in a draft, and `publish` promotes it. A template can't be used for sending until it's published.\n\n```ts\nconst template = await anypost.templates.create({\n  name: \"Welcome email\",\n  kind: \"html\",\n  html: \"<h1>Welcome, {{ name }}</h1>\",\n});\n\nawait anypost.templates.publish(template.id);\n```\n\n`kind` (`html` or `markdown`) is immutable once set; the plain-text body is always derived server-side. `getDraft`, `updateDraft`, `deleteDraft`, `duplicate`, `get`, `update` (name only), and `delete` round out the resource. Send a published template with `template_id` (see [Sending](#sending)). See [Templates](https://anypost.com/docs/reference/templates) for the full model.\n\n## Suppressions\n\nA suppression blocks sends to an address, scoped to a `topic`. The wildcard `*` blocks every topic; a named topic (e.g. `marketing`) leaves transactional traffic untouched.\n\n```ts\nawait anypost.suppressions.create({\n  email: \"alice@example.com\",\n  topic: \"marketing\",\n  note: \"Customer requested removal\",\n});\n\nawait anypost.suppressions.delete(\"alice@example.com\", \"marketing\");\n```\n\n`get`, `list` (with `email_contains`, `topic`, `reason`, and `origin` filters), `listForEmail`, and `deleteForEmail` round out the resource. See [Suppressions](https://anypost.com/docs/reference/suppressions) for scoping and the automatic-suppression rules for bounces and complaints.\n\n## Webhooks\n\nManage webhook subscriptions under `anypost.webhooks`. The `signing_secret` comes back only once, on `create`; later reads return only `signing_secret_prefix`.\n\n```ts\nconst webhook = await anypost.webhooks.create({\n  name: \"Production events\",\n  url: \"https://hooks.example.com/anypost\",\n  events: [\"email.delivered\", \"email.bounced\", \"email.complained\"],\n});\nconsole.log(webhook.signing_secret); // store now; never retrievable again\n```\n\n`update`, `test`, `rotateSecret`, `get`, `list`, and `delete` round out the resource. See [Webhooks](https://anypost.com/docs/reference/webhooks) for the event catalog, status transitions, and the secret-rotation grace window.\n\n### Verifying deliveries\n\n`verifyWebhookSignature` is a standalone function. It needs the signing secret, not an API key, so call it in your handler without a client. Pass the **raw** request body (the exact bytes, before JSON parsing), the `Anypost-Signature` header, and the secret. It resolves on success and throws `WebhookVerificationError` otherwise. `unwrapWebhookEvent` does the same and returns the parsed `WebhookDelivery`.\n\n```ts\nimport { unwrapWebhookEvent, WebhookVerificationError } from \"anypost\";\n\ntry {\n  const delivery = await unwrapWebhookEvent(rawBody, signatureHeader, secret);\n  for (const event of delivery.events) {\n    console.log(event.type, event.data.email_id);\n  }\n} catch (err) {\n  if (err instanceof WebhookVerificationError) {\n    // err.reason: \"no_match\" | \"timestamp_out_of_tolerance\" | ...\n    return res.status(400).end();\n  }\n  throw err;\n}\n```\n\nReach for `verifyWebhookSignature` when something else has already parsed the body. Keep the raw bytes for the verify step (a captured `req.rawBody`, say), then use your framework's parsed object once it passes:\n\n```ts\nimport { verifyWebhookSignature, WebhookVerificationError } from \"anypost\";\n\napp.post(\"/anypost\", async (req, res) => {\n  try {\n    await verifyWebhookSignature(req.rawBody, req.header(\"Anypost-Signature\"), secret);\n  } catch (err) {\n    if (err instanceof WebhookVerificationError) return res.status(400).end();\n    throw err;\n  }\n  for (const event of req.body.events) handle(event); // req.body is already parsed\n  res.status(204).end();\n});\n```\n\nDeliveries older than five minutes are rejected by default to bound replay; pass `toleranceSeconds` to widen, narrow, or disable that check. During a secret rotation the header carries a `v1=` component per active secret, and a match on any one passes, so deliveries keep verifying while you redeploy.\n\n## Events\n\n`anypost.events.list` pages the team's event stream, newest-first. The window defaults to the last 24 hours and is clamped to your plan's retention. Events are read-only and not addressable by id, so there is no `get`.\n\n```ts\nfor await (const event of await anypost.events.list({ event_type: \"email.bounced\" })) {\n  console.log(event.occurred_at, event.recipient, event.bounce_classification);\n}\n```\n\nFilter by `start`, `end`, `event_type`, `recipient`, `email_id`, `message_id`, `domain`, `topic`, `campaign`, `template_id`, `ip_pool`, and `tags`, a `string[]` that matches an event carrying *any* of the given tags. Every other filter is exact-match. This is also how you backfill the gap after a webhook endpoint was disabled: page the events that occurred during the outage once it's healthy. See [Events](https://anypost.com/docs/reference/events) for the field reference.\n\n## Pagination\n\nList endpoints return a `Page`. Read one page directly, or iterate it with `for await` to walk every page; the client fetches each one as needed.\n\n```ts\nconst page = await anypost.domains.list({ limit: 50 });\npage.data;        // this page's items\npage.has_more;    // whether another page exists\npage.next_cursor; // pass as `after` to fetch it yourself\n\nfor await (const domain of await anypost.domains.list()) {\n  console.log(domain.name); // every domain, across all pages\n}\n```\n\n## Errors\n\nA failed request throws an `AnypostError` subclass. Branch on `error.type`, the stable machine-readable code, not on the HTTP status.\n\n```ts\nimport { AnypostError, ValidationError, RateLimitError } from \"anypost\";\n\ntry {\n  await anypost.email.send({ from, to, subject, html });\n} catch (err) {\n  if (err instanceof ValidationError) {\n    console.error(err.errors); // { from: [\"The from field is required.\"] }\n  } else if (err instanceof RateLimitError) {\n    console.error(err.retryAfterMs);\n  } else if (err instanceof AnypostError) {\n    console.error(err.type, err.status, err.message);\n  }\n}\n```\n\n| Class | `type` | Status |\n|---|---|---|\n| `ValidationError` | `validation_error` | `400`, `422` |\n| `AuthenticationError` | `authentication_error` | `401` |\n| `PermissionError` | `permission_error` | `403` |\n| `NotFoundError` | `not_found` | `404` |\n| `ConflictError` | `conflict`, `idempotency_concurrent`, `webhook_rotation_in_progress` | `409` |\n| `IdempotencyMismatchError` | `idempotency_mismatch` | `422` |\n| `RateLimitError` | `rate_limit_exceeded` | `429` |\n| `PayloadTooLargeError` | `payload_too_large` | `413` |\n| `APIError` | `internal_error`, `provisioning_error` | `5xx` |\n| `APIConnectionError` | `connection_error` | none |\n\nEvery error carries `type`, `status`, `message`, and the parsed `raw` body.\n\n## Retries and idempotency\n\nThe client retries `429`, `502`, `503`, and network failures up to `maxRetries` times (default 2), with exponential backoff and full jitter. It honors `Retry-After`.\n\nSends are made safe to retry automatically: when retries are enabled and you do not pass an `idempotencyKey`, the client generates one and reuses it across attempts, so a retried send cannot deliver twice. Pass your own key to dedupe across process restarts:\n\n```ts\nawait anypost.email.send(message, { idempotencyKey: orderId });\n```\n\n## Configuration\n\n```ts\nnew Anypost(\"ap_your_api_key\", options);\nnew Anypost({ apiKey: \"ap_your_api_key\", ...options });\n```\n\n| Option | Default | Description |\n|---|---|---|\n| `apiKey` | `ANYPOST_API_KEY` | Bearer credential (`ap_...`). |\n| `baseUrl` | `https://api.anypost.com/v1` | API base URL. |\n| `timeoutMs` | `30000` | Per-request timeout. |\n| `maxRetries` | `2` | Automatic retries for transient failures. |\n| `fetch` | global `fetch` | Custom fetch implementation. |\n| `headers` | `{}` | Extra headers sent on every request. |\n\nPer-call options (`idempotencyKey`, `headers`, `maxRetries`, `timeoutMs`, `signal`) override the client defaults for a single request.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}