From 737120170c735db04074d6ceefb8d3838623361b Mon Sep 17 00:00:00 2001 From: Rajat Saxena Date: Sun, 15 Nov 2026 20:03:50 +0530 Subject: [PATCH] Add tags, general settings, overview and test sends to the SDK Platforms that build on SendLit (CourseLit first) still call these endpoints by hand: listing, renaming and deleting tags with their references, reading and setting the mailing address, the dashboard overview, and sending a test of a broadcast or sequence email. --- .changeset/sdk-tags-settings-overview.md | 5 +++ packages/sendlit/README.md | 5 ++- packages/sendlit/src/client.ts | 12 ++++++ packages/sendlit/src/index.ts | 3 ++ packages/sendlit/src/resources.test.ts | 37 ++++++++++++++++++ packages/sendlit/src/resources/overview.ts | 17 ++++++++ packages/sendlit/src/resources/sequences.ts | 19 +++++++++ packages/sendlit/src/resources/settings.ts | 29 ++++++++++++++ packages/sendlit/src/resources/tags.ts | 43 +++++++++++++++++++++ 9 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 .changeset/sdk-tags-settings-overview.md create mode 100644 packages/sendlit/src/resources/overview.ts create mode 100644 packages/sendlit/src/resources/settings.ts create mode 100644 packages/sendlit/src/resources/tags.ts diff --git a/.changeset/sdk-tags-settings-overview.md b/.changeset/sdk-tags-settings-overview.md new file mode 100644 index 00000000..b21c1917 --- /dev/null +++ b/.changeset/sdk-tags-settings-overview.md @@ -0,0 +1,5 @@ +--- +"sendlit": minor +--- + +Add `tags` (list, references, rename, remove), `settings.general` (get, update), `overview.get`, and `sequences.sendEmailTest`, so platforms can manage tags, set the mailing address, show dashboard numbers and send test emails without calling the API by hand. diff --git a/packages/sendlit/README.md b/packages/sendlit/README.md index bf3a9c01..a57d0d4b 100644 --- a/packages/sendlit/README.md +++ b/packages/sendlit/README.md @@ -63,12 +63,15 @@ new SendLit({ | `sendlit.contacts` | `create`, `list`, `listAll`, `get`, `update`, `remove`, `addTag`, `removeTag`, `deliveries`, `events` | | `sendlit.segments` | `create`, `list`, `get`, `update`, `remove` | | `sendlit.templates` | `list`, `listSystem`, `get`, `create`, `update`, `duplicate`, `remove` | -| `sendlit.sequences` | Broadcasts and sequences: `create`, `list`, `listAll`, `get`, `update`, `remove`, `addEmail`, `updateEmail`, `publishChanges`, `discardChanges`, `emailVersions`, `restoreEmailVersion`, `removeEmail`, `start`, `pause`, `stats`, `subscribers` | +| `sendlit.sequences` | Broadcasts and sequences: `create`, `list`, `listAll`, `get`, `update`, `remove`, `addEmail`, `updateEmail`, `publishChanges`, `discardChanges`, `emailVersions`, `restoreEmailVersion`, `removeEmail`, `start`, `pause`, `stats`, `subscribers`, `sendEmailTest` | | `sendlit.emails` | Transactional email: `send`, `get`, `list`, `listAll` | | `sendlit.events` | Custom events: `track`, and `definitions.list`, `definitions.get`, `definitions.create`, `definitions.update`, `definitions.delete` | | `sendlit.provisioning` | Organization key only: `provisionTeam`, `getTeam`, `updateTeam`, `createTeamKey`, `suspendTeam`, `resumeTeam`, `archiveTeam`, `getTeamUsage` | | `sendlit.suppressions` | `list`, `listAll`, `get`, `release` | | `sendlit.deliveryEvents` | `list`, `listAll`, `get` | +| `sendlit.tags` | `list`, `references`, `rename`, `remove` | +| `sendlit.settings` | `general.get`, `general.update` (the mailing address broadcasts and sequences need) | +| `sendlit.overview` | `get`: the team's dashboard numbers, optionally for `rangeDays` 1, 3, 7 or 30 | Request and response types are exported too, for example `Contact`, `CreateContactInput`, `Sequence`, and `SendEmailInput`. diff --git a/packages/sendlit/src/client.ts b/packages/sendlit/src/client.ts index cdbb5149..f9b287c6 100644 --- a/packages/sendlit/src/client.ts +++ b/packages/sendlit/src/client.ts @@ -4,10 +4,13 @@ import { Contacts } from "./resources/contacts"; import { DeliveryEvents } from "./resources/delivery-events"; import { Emails } from "./resources/emails"; import { Events } from "./resources/events"; +import { OverviewResource } from "./resources/overview"; import { Provisioning } from "./resources/provisioning"; import { Segments } from "./resources/segments"; import { Sequences } from "./resources/sequences"; +import { Settings } from "./resources/settings"; import { Suppressions } from "./resources/suppressions"; +import { Tags } from "./resources/tags"; import { Templates } from "./resources/templates"; import { assertId } from "./validate"; @@ -62,6 +65,12 @@ export class SendLit { readonly provisioning: Provisioning; readonly suppressions: Suppressions; readonly deliveryEvents: DeliveryEvents; + /** Tags across the team's contacts: list, rename and delete them. */ + readonly tags: Tags; + /** Team settings, such as the mailing address. */ + readonly settings: Settings; + /** The team's dashboard numbers. */ + readonly overview: OverviewResource; constructor(options: SendLitOptions = {}) { const g = globalThis as { window?: { document?: unknown } }; @@ -121,5 +130,8 @@ export class SendLit { this.provisioning = new Provisioning(http); this.suppressions = new Suppressions(http); this.deliveryEvents = new DeliveryEvents(http); + this.tags = new Tags(http); + this.settings = new Settings(http); + this.overview = new OverviewResource(http); } } diff --git a/packages/sendlit/src/index.ts b/packages/sendlit/src/index.ts index c87f5a75..739ba0b7 100644 --- a/packages/sendlit/src/index.ts +++ b/packages/sendlit/src/index.ts @@ -4,8 +4,11 @@ export * from "./resources/contacts"; export * from "./resources/delivery-events"; export * from "./resources/emails"; export * from "./resources/events"; +export * from "./resources/overview"; export * from "./resources/provisioning"; export * from "./resources/segments"; export * from "./resources/sequences"; +export * from "./resources/settings"; export * from "./resources/suppressions"; +export * from "./resources/tags"; export * from "./resources/templates"; diff --git a/packages/sendlit/src/resources.test.ts b/packages/sendlit/src/resources.test.ts index 960e242b..e226ab6d 100644 --- a/packages/sendlit/src/resources.test.ts +++ b/packages/sendlit/src/resources.test.ts @@ -269,6 +269,43 @@ const cases: Case[] = [ query: "?page=1&limit=20", }, ], + [ + "sequences.sendEmailTest", + (s) => s.sequences.sendEmailTest("seq_1", "eml_1", { to: ["ada@example.com"] }), + { + method: "POST", + path: "/sequences/seq_1/emails/eml_1/test", + body: { to: ["ada@example.com"] }, + }, + ], + ["tags.list", (s) => s.tags.list(), { method: "GET", path: "/tags" }], + [ + "tags.references", + (s) => s.tags.references("early bird"), + { method: "GET", path: "/tags/early%20bird/references" }, + ], + [ + "tags.rename", + (s) => s.tags.rename("vip", { name: "VIP" }), + { method: "PATCH", path: "/tags/vip", body: { name: "VIP" } }, + ], + ["tags.remove", (s) => s.tags.remove("vip"), { method: "DELETE", path: "/tags/vip" }], + [ + "settings.general.get", + (s) => s.settings.general.get(), + { method: "GET", path: "/settings/general" }, + ], + [ + "settings.general.update", + (s) => s.settings.general.update({ mailingAddress: "1 Main St" }), + { method: "PUT", path: "/settings/general", body: { mailingAddress: "1 Main St" } }, + ], + ["overview.get", (s) => s.overview.get(), { method: "GET", path: "/overview" }], + [ + "overview.get with range", + (s) => s.overview.get({ rangeDays: 30 }), + { method: "GET", path: "/overview", query: "?rangeDays=30" }, + ], [ "emails.send", (s) => diff --git a/packages/sendlit/src/resources/overview.ts b/packages/sendlit/src/resources/overview.ts new file mode 100644 index 00000000..f1e79e93 --- /dev/null +++ b/packages/sendlit/src/resources/overview.ts @@ -0,0 +1,17 @@ +import { contract } from "@sendlit/api-contract"; +import type { HttpClient, Ok, Query } from "../http"; + +const routes = contract.overview; + +/** The team's dashboard numbers: contacts, sends, opens and clicks over a + * period. */ +export type Overview = Ok; +export type OverviewQuery = Query; + +export class OverviewResource { + constructor(private readonly http: HttpClient) {} + + async get(query: OverviewQuery = {}): Promise { + return this.http.request(routes.get, { query }); + } +} diff --git a/packages/sendlit/src/resources/sequences.ts b/packages/sendlit/src/resources/sequences.ts index e22a7d0a..4d2af099 100644 --- a/packages/sendlit/src/resources/sequences.ts +++ b/packages/sendlit/src/resources/sequences.ts @@ -17,6 +17,8 @@ export type DiscardSequenceChangesInput = Body; export type SequenceEmailVersion = Ok["items"][number]; export type ListSequencesQuery = Query; export type ListSequenceSubscribersQuery = Query; +export type SendSequenceEmailTestInput = Body; +export type SequenceEmailTestResult = Ok; export class Sequences { constructor(private readonly http: HttpClient) {} @@ -158,6 +160,23 @@ export class Sequences { }); } + /** Sends a test of one broadcast or sequence email, rendered like a real + * send with a `[Test]` subject prefix. Tests count against the team's test + * limits (429 `test_send_limit_reached` / `test_send_email_limit_reached`). */ + async sendEmailTest( + sequenceId: string, + emailId: string, + input: SendSequenceEmailTestInput, + ): Promise { + return this.http.request(routes.sendEmailTest, { + params: { + sequenceId: assertId(sequenceId, "sequenceId"), + emailId: assertId(emailId, "emailId"), + }, + body: input, + }); + } + /** IDs of contacts that have received this broadcast or sequence. */ async subscribers( sequenceId: string, diff --git a/packages/sendlit/src/resources/settings.ts b/packages/sendlit/src/resources/settings.ts new file mode 100644 index 00000000..565328d7 --- /dev/null +++ b/packages/sendlit/src/resources/settings.ts @@ -0,0 +1,29 @@ +import { contract } from "@sendlit/api-contract"; +import type { Body, HttpClient, Ok } from "../http"; + +const routes = contract.settings.general; + +/** The team's general settings, such as the mailing address that broadcasts + * and sequences need before they can send. */ +export type GeneralSettings = Ok; +export type UpdateGeneralSettingsInput = Body; + +export class GeneralSettingsResource { + constructor(private readonly http: HttpClient) {} + + async get(): Promise { + return this.http.request(routes.get); + } + + async update(input: UpdateGeneralSettingsInput): Promise { + return this.http.request(routes.update, { body: input }); + } +} + +export class Settings { + readonly general: GeneralSettingsResource; + + constructor(http: HttpClient) { + this.general = new GeneralSettingsResource(http); + } +} diff --git a/packages/sendlit/src/resources/tags.ts b/packages/sendlit/src/resources/tags.ts new file mode 100644 index 00000000..e3446c79 --- /dev/null +++ b/packages/sendlit/src/resources/tags.ts @@ -0,0 +1,43 @@ +import { contract } from "@sendlit/api-contract"; +import type { Body, HttpClient, Ok } from "../http"; +import { encodeSegment } from "../validate"; + +const routes = contract.tags; + +/** A tag in use in the team, with how many contacts carry it. */ +export type Tag = Ok["items"][number]; +/** Where a tag is still used (segments, sequence triggers, ...), so it can be + * shown before the tag is renamed or deleted. */ +export type TagReferences = Ok; +export type RenameTagInput = Body; +export type RenameTagResult = Ok; +export type RemoveTagResult = Ok; + +export class Tags { + constructor(private readonly http: HttpClient) {} + + async list(): Promise> { + return this.http.request(routes.list); + } + + async references(tag: string): Promise { + return this.http.request(routes.references, { + params: { tag: encodeSegment(tag, "tag") }, + }); + } + + /** Renames a tag on every contact and everywhere it's referenced. */ + async rename(tag: string, input: RenameTagInput): Promise { + return this.http.request(routes.rename, { + params: { tag: encodeSegment(tag, "tag") }, + body: input, + }); + } + + /** Removes a tag from every contact. */ + async remove(tag: string): Promise { + return this.http.request(routes.remove, { + params: { tag: encodeSegment(tag, "tag") }, + }); + } +}