{"_id":"@apex-inc/transactional","name":"@apex-inc/transactional","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@apex-inc/transactional","version":"0.2.0","description":"Apex transactional email client for Node 18+ and modern runtimes (Edge, Deno, Bun, browsers).","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build && npm test"},"keywords":["apex","transactional","email","ses","sandbox","idempotency"],"license":"MIT","engines":{"node":">=18"},"devDependencies":{"typescript":"^5.7.0","vitest":"^3.2.4"},"_id":"@apex-inc/transactional@0.2.0","gitHead":"b792e7d1e69de6bc79b990511b64b7f5a955335f","_nodeVersion":"23.11.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-QBb8/D3WPjbFSKz9GFesBCaiaELzcMkeX79bi2ImtjRbgC6D7zdEev2KuJdS42Uo7qX+9vR+W/nJm9N6WZ6KuQ==","shasum":"52c2b2bcf5fe4de83390de71a99a0f47aa632d3c","tarball":"https://registry.npmjs.org/@apex-inc/transactional/-/transactional-0.2.0.tgz","fileCount":8,"unpackedSize":23374,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICc+Z14ljNiOS4D9oZ3g+IkXPC5kWaoYx7Yti2l9tYIQAiAuI1RHXplHJDlUhlcFSutDXXaKz7A1IpetCUPXCPyHpQ=="}]},"_npmUser":{"name":"apex.inc","email":"chris@apex.inc"},"directories":{},"maintainers":[{"name":"apex.inc","email":"chris@apex.inc"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/transactional_0.2.0_1777728795050_0.3537820424623015"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-02T13:33:14.948Z","0.2.0":"2026-05-02T13:33:15.205Z","modified":"2026-05-02T13:33:15.451Z"},"maintainers":[{"name":"apex.inc","email":"chris@apex.inc"}],"description":"Apex transactional email client for Node 18+ and modern runtimes (Edge, Deno, Bun, browsers).","keywords":["apex","transactional","email","ses","sandbox","idempotency"],"license":"MIT","readme":"# @apex-inc/transactional\n\nApex transactional email client for **Node 18+**, modern runtimes (Edge, Deno, Bun), and browsers.\n\nWorks against `POST /api/v1/transactional/send` using a workspace **`apex_tx_*`** secret. Built-in retries with **safe Idempotency-Key replay**, sandbox helpers, and typed errors.\n\n## Install\n\n```bash\nnpm install @apex-inc/transactional\n```\n\n## Quickstart\n\n```ts\nimport { ApexTransactionalClient } from \"@apex-inc/transactional\";\n\nconst client = new ApexTransactionalClient({\n  baseUrl: \"https://app.apex.inc\",\n  apiKey: process.env.APEX_TX_KEY!, // apex_tx_…\n});\n\nconst result = await client.send({\n  to: \"alex@customer.com\",\n  subject: \"You've been invited\",\n  html: \"<p>Welcome aboard!</p>\",\n  text: \"Welcome aboard!\",\n});\n\nconsole.log(result.id, result.messageId);\n```\n\n## Sandbox\n\n```ts\nimport { ApexTransactionalClient, sandboxRecipient } from \"@apex-inc/transactional\";\n\nconst sandbox = new ApexTransactionalClient({\n  baseUrl: \"https://app.apex.inc\",\n  apiKey: process.env.APEX_TX_KEY!,\n  sandbox: true, // forces X-Apex-Mode: sandbox on every call\n});\n\nawait sandbox.send({\n  to: sandboxRecipient(\"bounce\"),\n  subject: \"Test bounce\",\n  text: \"ignored\",\n}); // -> throws ApexSendFailedError code=\"bounce\"\n```\n\n## Retries + idempotency\n\nThe client always sends an `Idempotency-Key` header. If you don't provide one, it generates a per-call key so transient retries (network errors, 5xx, 429) can't cause duplicates. The same key is reused across retry attempts.\n\n```ts\nimport {\n  ApexAuthError,\n  ApexRateLimitError,\n  ApexSendFailedError,\n} from \"@apex-inc/transactional\";\n\ntry {\n  await client.send({ to, subject, text });\n} catch (err) {\n  if (err instanceof ApexAuthError) /* rotate keys */;\n  if (err instanceof ApexRateLimitError) /* back off */;\n  if (err instanceof ApexSendFailedError) /* err.code: bounce | suppressed | send_failed */;\n}\n```\n\n## Configuration\n\n| Option | Default | Notes |\n|---|---|---|\n| `baseUrl` | — | required |\n| `apiKey` | — | required, must start with `apex_tx_` |\n| `sandbox` | `false` | per-call override available |\n| `timeoutMs` | `30000` | per request |\n| `maxRetries` | `3` | total attempts (initial + retries) |\n| `retryBaseDelayMs` | `250` | exponential backoff with full jitter |\n| `retryMaxDelayMs` | `5000` | cap |\n| `fetchFn` | global `fetch` | inject for tests / custom transports |\n\n## Error types\n\n- `ApexAuthError` (401)\n- `ApexValidationError` (400)\n- `ApexIdempotencyMismatchError` (409 — same key, different payload)\n- `ApexIdempotencyInFlightError` (409 — concurrent retry)\n- `ApexRateLimitError` (429)\n- `ApexSendFailedError` (422 — bounce / suppressed / send_failed)\n- `ApexServerError` (5xx)\n- `ApexNetworkError` / `ApexTimeoutError`\n- `ApexError` (base)\n\nAll errors expose `status`, `code`, `requestId`, and the raw decoded `body`.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-af1e4c2359655484814fcb6e11c6a659"}