Skip to content

feat!: Lettermint PHP SDK 3.0 - #43

Merged
bjarn merged 6 commits into
mainfrom
feat/v3
Oct 4, 2026
Merged

bjarn merged 6 commits into
mainfrom
feat/v3

Conversation

@bjarn

@bjarn bjarn commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A new major version, following the Lettermint SDK design agreed for all languages. The reference is the Node.js SDK 3.0 (lettermint/lettermint-node#36). Sending holds no state on the client, types are generated from the published API spec, and every SDK passes the same conformance suite.

What changes

  • One client with sendingToken and/or teamToken. A single token string is detected by prefix: lm_team_ is a team token, lm_ plus letters and digits is a sending token, and anything else is a config error. Each part uses its own token and never falls back to the other.
  • Stateless sending: emails.send(message, options), emails.sendBatch(…) and an immutable per-message compose() builder. The idempotency key is a per-call option. Attachments are objects.
  • Typed errors with the HTTP status and API error code, plus timeout, connection, unexpected-response and redirect errors.
  • Transport: redirects are never followed, so the token never reaches another host. The timeout covers the whole request, nothing is retried automatically, and tokens never appear in logs or debug output.
  • Generated types from the published API spec (lettermint/lettermint#2582, pinned to 0b4ecbdd23) using its names. Enums are open, so new API values never break decoding.
  • Team API: same layout in every SDK, with typed queries and cursor iteration.
  • Webhooks: verify(rawBody, headers) requires both X-Lettermint-Signature and X-Lettermint-Delivery, accepts any matching v1, uses a ±300 s tolerance, and keys the HMAC on the secret as-is. Reason codes are the same in every SDK.
  • UPGRADE.md has before/after for every changed call, the full type rename table, and a ready-to-copy coding-agent instruction. The previous major no longer receives updates.

Specific to this SDK

  • new Lettermint(sendingToken: …, teamToken: …); $lettermint->emails->send([...], idempotencyKey: …); immutable compose().
  • Fixes the 2.x issues:
    • A cached builder leaked a half-built email into the next send, notably in Octane and queue workers.
    • Every error was a generic \Exception with code 0.
    • Redirects forwarded the token.
    • Tokens were visible in var_dump and stack traces; they now use #[\SensitiveParameter] and redaction.
  • PHP ^8.2, Guzzle 7 or 8. PHPStan raised to level 10.
  • lettermint/lettermint-laravel 3.0 depends on this release (feat!: Lettermint Laravel 3.0 lettermint-laravel#45).

Verification

  • 287 Pest tests pass on PHP 8.2–8.5 with lowest and stable dependencies, including real-network tests for redirects and timeouts.
  • Conformance: 42/42 with --strict. 2.8.0 fails 11.

Release

Tag 3.0.0 (unprefixed, as before) before releasing Laravel 3.0, which requires ^3.0.

Docs for all SDK majors: lettermint/lettermint#2594 (stacked on lettermint/lettermint#2590). Merge it after the releases.

🤖 Generated with Claude Code

bjarn added 5 commits October 3, 2026 23:59
src/Types is written by the private lettermint/sdk-generator (emit php,
naming profile next, spec 80d8ab2a2d). composer generate
regenerates it; composer generate:check verifies it, or only the headers
when the generator is not available (CI).
… tokens

One client, new Lettermint(sendingToken: ..., teamToken: ...) or
new Lettermint($token), replaces Lettermint::email(), Lettermint::api() and
the cached $lettermint->email builder.

- emails->send(array, idempotencyKey:), emails->sendBatch() and an immutable
  emails->compose() builder; no message state on the client.
- Each part uses its own token and never falls back to the other.
- Typed exceptions under Lettermint\Exceptions, all extending
  LettermintException; ApiException::getCode() is the HTTP status.
- Redirects are never followed, the timeout covers the whole request, no
  retries; empty and HTML responses raise UnexpectedResponseException.
- Tokens and webhook secrets are held in SensitiveParameterValue, token
  parameters are #[\SensitiveParameter], debug output is redacted and the
  objects cannot be serialized.
- The base URL path is kept, and empty, "." and ".." ids are rejected.
- Generated response classes (Lettermint\Types) with open enums, typed
  CursorPage lists and iterate() helpers; typed array shapes for requests.
- Webhook::verify(rawBody, headers) requires both signature headers and
  returns a WebhookPayload; failures carry a reason code.
- PHPStan level 10; the Laravel-only dev dependencies are dropped.

BREAKING CHANGE: see UPGRADE.md for every changed call.
The spec at lettermint/lettermint@0b4ecbdd23 (main, the #2582 merge) is
identical to the previous pin; only the generated file headers change.
Windows runners check out files with CRLF, so the header check split on
\n left a trailing \r.
@bjarn
bjarn enabled auto-merge (squash) October 4, 2026 10:52
@bjarn
bjarn merged commit de94bfe into main Oct 4, 2026
36 checks passed
@bjarn
bjarn deleted the feat/v3 branch October 4, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants