Skip to content

feat!: Lettermint Elixir SDK 2.0 - #10

Merged
bjarn merged 7 commits into
mainfrom
feat/v2
Oct 4, 2026
Merged

bjarn merged 7 commits into
mainfrom
feat/v2

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

  • Includes the release fix: the Hex version now comes from the tag. Hex was stuck at 1.0.0 because mix.exs still said 1.0.0.
  • Includes the webhook fix: an empty secret is rejected.
  • Elixir ~> 1.15 and OTP 25+. Calls return {:ok, _} / {:error, exception}; lists stream via iterate.
  • Tokens are hidden from inspect, including structs: false.

Verification

  • 106 tests pass.
  • Conformance: 42/42 with --strict. The Hex 1.0.0 fails 5.
  • Not run locally: the Elixir 1.15/OTP 25 matrix entries. CI covers them.

Release

Publish a GitHub release v2.0.0. It publishes the first release since 1.0.0 to Hex.

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

🤖 Generated with Claude Code

bjarn added 7 commits October 3, 2026 13:51
Releases v1.1.0 and v1.2.0 failed at "Check release version" because
mix.exs was never bumped by hand. Rewrite @Version from the release tag
before testing and publishing, matching the Node and Python SDK release flow.
An empty secret let anyone compute a valid HMAC. Return
{:error, :invalid_signature} when the secret is nil or empty.
…ator

lib/lettermint/generated/ is emitted by lettermint/sdk-generator (emit elixir,
next profile, spec 80d8ab2a2d). scripts/generate.sh regenerates or
checks it when the generator is available, and otherwise verifies the
generated headers.
One client holds the sending and the team token (Lettermint.new/1,2, with
token format detection for a bare string); Lettermint.Emails uses the sending
token, every other module the team token, and ping/reschedule/cancel either.
A missing token raises Lettermint.ConfigError that names the option.

- The Team API follows the shared layout: Projects.ReportForwarding,
  Team.Members and Webhooks.Deliveries modules, iterate functions that return
  a Stream, and nested query maps serialized like the Node SDK.
- One exception struct per error kind (APIError and its per-status variants,
  TimeoutError, ConnectionError with the HTTP client's reason,
  UnexpectedResponseError, RedirectError, ClientValidationError, ConfigError,
  WebhookVerificationError); calls return {:error, error}.
- The timeout covers the whole request; an empty 2xx body where JSON is
  expected and a body that cannot be encoded as JSON are errors.
- Tokens and webhook secrets live in closures, so no debug output shows them.
- Webhook verification takes the headers, requires X-Lettermint-Delivery and
  returns a WebhookPayload.
- Types come from the generated Lettermint.Types modules; the in-repo
  generator, Lettermint.Models, the operations manifest and
  Lettermint.MessageTag are removed. Requires Erlang/OTP 25+.

BREAKING CHANGE: Lettermint.email/2, Lettermint.api/2, Lettermint.Email,
Lettermint.API, Lettermint.Models, Lettermint.Model, Lettermint.MessageTag and
%Lettermint.Error{} are removed; see UPGRADE.md.
The spec at lettermint/lettermint@0b4ecbdd23 (main, the #2582 merge) is
identical to the previous pin; only the generated file headers change.
@bjarn
bjarn requested a review from a team as a code owner October 3, 2026 22:51
@bjarn
bjarn merged commit a0991ae into main Oct 4, 2026
4 checks passed
@bjarn
bjarn deleted the feat/v2 branch October 4, 2026 11:16
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