Skip to content

refactor(api): talk to sendlit through the official sdk - #887

Open
clbotdev wants to merge 4 commits into
feat/plan-limitsfrom
refactor/sendlit-sdk
Open

clbotdev wants to merge 4 commits into
feat/plan-limitsfrom
refactor/sendlit-sdk

Conversation

@clbotdev

@clbotdev clbotdev commented Nov 15, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #881 (feat/plan-limits). Uses sendlit 0.5.0, which includes codelitdev/sendlit#203 (the tags, general settings, overview and test-send methods CourseLit needs).

Why

apps/api/src/sendlit-client.ts called SendLit's API by hand, with its own response types. Those had drifted from SendLit's contract, and the test fakes drifted with them. The official SDK's types come from SendLit's contract, so they can't drift.

What changes

  • sendlit-client.ts goes from 1,367 to 701 lines. Every exported function keeps its signature, so callers don't change.
  • Errors: the SDK's SendLitError is mapped to SendLitApiError in one place, which gains a code field. The quota-exhausted and plan_limit_exceeded checks use that code instead of matching message text.
  • No automatic retries (the outbox already retries) and the same 15s timeout. The injectable fetcher becomes the SDK's fetch.
  • Removed getOverview/getSendLitOverview and updateSubscriber: nothing called them, and the overview type didn't match SendLit's.
  • Dropped the id/name aliases the client added to sequences and templates. CourseLit's contract declares these responses as passthrough, so OpenAPI is unchanged, and every admin read falls back to sequenceId/templateId/title.
  • Fields SendLit's contract doesn't accept are no longer sent: the extra name on template create, purpose on template update.
  • Test fakes return SendLit's real response shapes and a JSON content type (sendlit-test-support.ts has builders). Fake org keys are sl_org_live_…. seq 1 became seq_1 in two tests, since the SDK validates ids.

Also fixed

  • A template created without content sent { content: { html: "" } }, which SendLit rejects. It now starts from SendLit's own system:blank starter (fetched from SendLit, not copied into CourseLit).
  • Listing broadcasts or sequences without a type reached SendLit and failed with its 400. type is now required in CourseLit's contract (GET /v1/school/mails/sequences, and the generated MCP tool), and the route answers 400 validation_failed itself. The admin always passes it.

Tests

tsc is clean and check:drift passes. All 23 test files that touch SendLit, plus the MCP and OpenAPI tests, pass (170 tests), run against the published sendlit 0.5.0.

sendlit-client.ts called SendLit's API by hand with its own response
types, and those had drifted from SendLit's contract. It now uses the
sendlit SDK, whose types come from that contract, and keeps only what
CourseLit adds: config, SendLitApiError and a little normalisation.

- errors map from the SDK's SendLitError in one place; the quota and
  plan-limit checks use SendLit's error code instead of message text
- no automatic retries (the outbox retries) and the same 15s timeout
- removed getOverview and updateSubscriber, which nothing called
- test fakes now return SendLit's real response shapes
@clbotdev
clbotdev marked this pull request as ready for review November 15, 2026 15:44
…quence type

- A template created without content sent { content: { html: "" } },
  which SendLit rejects. It now starts from SendLit's own system:blank
  starter.
- Listing broadcasts or sequences without a type reached SendLit, which
  requires one, and failed with its 400. The type is now required in the
  API contract and checked before calling SendLit.
…sendlit-sdk

# Conflicts:
#	apps/api/src/sendlit-client.ts

This branch has not been deployed

No deployments
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