feat(webhooks)!: add Basic Auth and required read flags - #35
Merged
Merged
Conversation
Bjornftw
approved these changes
Oct 3, 2026
bjarn
added a commit
that referenced
this pull request
Oct 4, 2026
## Summary
Lettermint Node SDK 3.0, a new major version. The main reason is safety:
in 2.x, `Lettermint.email(token)` returned one mutable builder per
client. Two emails built at the same time on one client could mix
recipients, content and `Idempotency-Key`, and an email abandoned
halfway leaked into the next send. 3.0 stores nothing about a message on
the client.
```ts
const lettermint = new Lettermint('lm_…'); // or { sendingToken }, { teamToken }, or both
await lettermint.emails.send({ from, to, subject, html }, { idempotencyKey: 'order-123' });
const base = lettermint.emails.compose().from('Acme <hi@acme.com>'); // immutable builder
for await (const domain of lettermint.domains.iterate()) { … }
const event = await new Webhook(secret).verify(rawBody, req.headers);
```
### What changes
* **One client.** `new Lettermint({ sendingToken, teamToken })` replaces
`Lettermint.email()` and `Lettermint.api()`. One token is enough.
* Each part uses its own token and never falls back to the other:
`emails.*` uses the sending token, the Team API uses the team token.
`ping()`, `messages.reschedule()` and `messages.cancel()` accept either.
* A single string is classified by format: `lm_team_` plus letters and
digits is a team token, `lm_` plus letters and digits is a sending
token. Anything else (such as `lm_sso_…`) throws.
* **Stateless sending.** `emails.send()`, `emails.sendBatch()`, and an
immutable `emails.compose()` builder where every setter returns a new
builder. The idempotency key is a per-call option. Attachments are
objects.
* **Typed errors.** `ApiError` (with `status` and API `code`) and its
subclasses for 401, 403, 404, 409, 422, 429 and 5xx. Also
`TimeoutError`, `ConnectionError`, `UnexpectedResponseError` (empty or
HTML bodies) and `RedirectError`. Class names survive the build.
* **Transport.**
* Redirects are never followed, so tokens never go to another host.
* The timeout covers the response body.
* Tokens never appear in `console.log`, `util.inspect` or
`JSON.stringify`.
* **Runtimes.** Node 20+, Bun, Deno and edge runtimes. No dependencies,
no `Buffer`, no `node:` imports.
* **Team API.** Typed query objects, `CursorPage<T>`, and `iterate()`
helpers. A few methods moved, e.g. `projects.routes(id)` →
`routes.list(projectId)`.
* **Webhooks.** `verify()` is async (Web Crypto). It requires both
`X-Lettermint-Signature` and `X-Lettermint-Delivery`, accepts any
matching `v1`, and accepts strings or bytes.
* **Types.** Generated from the current API spec
(lettermint/lettermint#2582) with its names, e.g. `ListDomainsResponse`,
`ProjectMutationResponse`, `ApiError`. Enums are open, so new API values
don't break typing.
* **Package.** ESM and CommonJS with default and named exports.
Unminified, without source maps (582 kB unpacked).
* **Upgrade with a coding agent:** `UPGRADE.md` has a ready-to-copy
instruction for Claude Code, Codex and similar tools. It ships in the
package, so agents can read the guide from `node_modules`.
* **2.x no longer receives updates.** `UPGRADE.md` covers every changed
call and the full type rename table. All 81 removed or renamed 2.x
exports are listed (checked by script).
* Includes the unreleased #35 (webhook Basic
Auth types).
### Release notes for the maintainer
* Release as `3.0.0` by publishing a GitHub release `v3.0.0`.
`release.yaml` sets the version from the tag. `package.json` says
`3.0.0-dev` on the branch.
* The docs update is a separate PR: lettermint/lettermint#2590. Merge it
after `3.0.0` is on npm.
## Verification
* 300 Jest tests pass on Node 20, 22, 24 and 26; a Bun run of the built
package also passes.
* Lint, type-check and build pass.
* `npm run test:smoke` packs the tarball and checks:
* the file list;
* ESM import and CJS require;
* types under `nodenext`;
* that tokens stay out of debug output.
* Shared conformance suite (private `lettermint/sdk-generator`): **42 of
42** scenarios pass with `--strict`. They cover concurrent and
half-built emails, unknown enum values, empty and HTML bodies,
redirects, timeouts, token redaction, token detection and 25 webhook
vectors. For comparison, 2.8.0 fails 7.
* README and UPGRADE snippets type-check against the build.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
Add typed webhook Basic Auth credentials and the safe has_basic_auth response flag. Preserve omitted, set and explicit-null request states. Keep empty passwords and credential whitespace. API requests still use Bearer authentication; Basic Auth applies to webhook delivery.
Source
Fresh strict export from monorepo main bfabb708ead47ee31922a7ae772e3d6376ac2ea4, after backend PR https://github.com/lettermint/lettermint/pull/2578. Splitter tests: 7 pass, none skipped. Both split documents match the checked-in docs. No OpenAPI document is included in this repository.
Sending SHA-256: c2aafb6d48a2572e688224629076748322f9c257394f18fe8879efb3f05059cb
Team SHA-256: e385c5f9b330633e943729ecb0e1b93fca96e84fecb2f111b3af8f0bafa0cc75
Verification
104 tests pass; type check, Biome lint/format and package build pass. A mocked Sandbox 403 test passes.
Existing scheduling, cursor, raw-pong and signed webhook tests remain in the suite. Existing public methods and compatibility helpers are retained.
No merge, deployment or package publication was performed.
Intentional typed-fixture migration
The API requires has_basic_auth in webhook detail, list and secret responses. Old typed fixtures without this field fail compilation. A TypeScript compiler test proves the three failures and verifies the migration: add has_basic_auth: false or true to match the fixture. Request credentials remain optional and nullable.