From 106e94527fa9465cd03bfbc4cda70720a6daa454 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 23 Jun 2026 14:38:33 +0300 Subject: [PATCH 1/4] Various new endpoints: OpenAPI 1.14.0 --- src/__tests__/LoopsClient.test.ts | 76 ++- src/index.ts | 789 ++++++++++++++++++++++++++---- 2 files changed, 733 insertions(+), 132 deletions(-) diff --git a/src/__tests__/LoopsClient.test.ts b/src/__tests__/LoopsClient.test.ts index 477c509..5166aab 100644 --- a/src/__tests__/LoopsClient.test.ts +++ b/src/__tests__/LoopsClient.test.ts @@ -897,6 +897,7 @@ describe("LoopsClient", () => { name: "Welcome Email", draftEmailMessageId: null, publishedEmailMessageId: "msg_pub_123", + transactionalGroupId: null, createdAt: "2023-01-01T00:00:00.000Z", updatedAt: "2023-01-02T00:00:00.000Z", dataVariables: ["name"], @@ -925,6 +926,7 @@ describe("LoopsClient", () => { draftEmailMessageId: "msg_draft_123", draftEmailMessageContentRevisionId: "rev_123", publishedEmailMessageId: null, + transactionalGroupId: null, createdAt: "2023-01-01T00:00:00.000Z", updatedAt: "2023-01-01T00:00:00.000Z", dataVariables: [], @@ -957,6 +959,7 @@ describe("LoopsClient", () => { name: "Updated Email", draftEmailMessageId: "msg_draft_123", publishedEmailMessageId: "msg_pub_123", + transactionalGroupId: null, createdAt: "2023-01-01T00:00:00.000Z", updatedAt: "2023-01-02T00:00:00.000Z", dataVariables: ["name"], @@ -990,6 +993,7 @@ describe("LoopsClient", () => { draftEmailMessageId: "msg_draft_123", draftEmailMessageContentRevisionId: "rev_123", publishedEmailMessageId: "msg_pub_123", + transactionalGroupId: null, createdAt: "2023-01-01T00:00:00.000Z", updatedAt: "2023-01-02T00:00:00.000Z", dataVariables: ["name"], @@ -1017,6 +1021,7 @@ describe("LoopsClient", () => { name: "Welcome Email", draftEmailMessageId: null, publishedEmailMessageId: "msg_pub_123", + transactionalGroupId: null, createdAt: "2023-01-01T00:00:00.000Z", updatedAt: "2023-01-02T00:00:00.000Z", dataVariables: ["name"], @@ -1112,7 +1117,6 @@ describe("LoopsClient", () => { describe("listThemes", () => { it("should list themes with pagination", async () => { const mockResponse = { - success: true, pagination: { totalResults: 1, returnedResults: 1, @@ -1123,7 +1127,7 @@ describe("LoopsClient", () => { }, data: [ { - themeId: "theme_123", + id: "theme_123", name: "Default", styles: { backgroundColor: "#ffffff" }, isDefault: true, @@ -1151,8 +1155,7 @@ describe("LoopsClient", () => { describe("getTheme", () => { it("should get a theme by ID", async () => { const mockResponse = { - success: true, - themeId: "theme_123", + id: "theme_123", name: "Default", styles: {}, isDefault: true, @@ -1178,7 +1181,6 @@ describe("LoopsClient", () => { describe("listComponents", () => { it("should list components with pagination", async () => { const mockResponse = { - success: true, pagination: { totalResults: 1, returnedResults: 1, @@ -1189,7 +1191,7 @@ describe("LoopsClient", () => { }, data: [ { - componentId: "comp_123", + id: "comp_123", name: "Header", lmx: "
", }, @@ -1214,8 +1216,7 @@ describe("LoopsClient", () => { describe("getComponent", () => { it("should get a component by ID", async () => { const mockResponse = { - success: true, - componentId: "comp_123", + id: "comp_123", name: "Header", lmx: "
", }; @@ -1235,10 +1236,23 @@ describe("LoopsClient", () => { }); }); + const campaignFixture = { + id: "camp_123", + name: "Spring announcement", + status: "Draft", + createdAt: "2025-01-01T00:00:00.000Z", + updatedAt: "2025-01-01T00:00:00.000Z", + emailMessageId: "msg_123", + campaignGroupId: null, + mailingListId: null, + audienceSegmentId: null, + audienceFilter: null, + scheduling: { method: "now" as const, timestamp: null }, + }; + describe("listCampaigns", () => { it("should list campaigns", async () => { const mockResponse = { - success: true, pagination: { totalResults: 1, returnedResults: 1, @@ -1247,17 +1261,7 @@ describe("LoopsClient", () => { nextCursor: null, nextPage: null, }, - data: [ - { - campaignId: "camp_123", - emailMessageId: "msg_123", - name: "Spring announcement", - subject: "", - status: "Draft", - createdAt: "2025-01-01T00:00:00.000Z", - updatedAt: "2025-01-01T00:00:00.000Z", - }, - ], + data: [campaignFixture], }; global.fetch = jest.fn().mockResolvedValue({ @@ -1278,13 +1282,7 @@ describe("LoopsClient", () => { describe("createCampaign", () => { it("should create a draft campaign", async () => { const mockResponse = { - success: true, - campaignId: "camp_123", - name: "Spring announcement", - status: "Draft", - createdAt: "2025-01-01T00:00:00.000Z", - updatedAt: "2025-01-01T00:00:00.000Z", - emailMessageId: "msg_123", + ...campaignFixture, emailMessageContentRevisionId: "rev_123", }; @@ -1308,15 +1306,7 @@ describe("LoopsClient", () => { describe("getCampaign", () => { it("should get a campaign by ID", async () => { - const mockResponse = { - success: true, - campaignId: "camp_123", - name: "Spring announcement", - status: "Draft", - createdAt: "2025-01-01T00:00:00.000Z", - updatedAt: "2025-01-01T00:00:00.000Z", - emailMessageId: "msg_123", - }; + const mockResponse = campaignFixture; global.fetch = jest.fn().mockResolvedValue({ ok: true, @@ -1336,13 +1326,9 @@ describe("LoopsClient", () => { describe("updateCampaign", () => { it("should update a draft campaign", async () => { const mockResponse = { - success: true, - campaignId: "camp_123", + ...campaignFixture, name: "Updated name", - status: "Draft", - createdAt: "2025-01-01T00:00:00.000Z", updatedAt: "2025-01-02T00:00:00.000Z", - emailMessageId: "msg_123", }; global.fetch = jest.fn().mockResolvedValue({ @@ -1368,14 +1354,14 @@ describe("LoopsClient", () => { describe("getEmailMessage", () => { it("should get an email message by ID", async () => { const mockResponse = { - success: true, - emailMessageId: "msg_123", + id: "msg_123", campaignId: "camp_123", subject: "Hello", previewText: "Preview", fromName: "Loops", fromEmail: "hello", replyToEmail: "", + emailFormat: "styled", lmx: "", contentRevisionId: "rev_123", updatedAt: "2025-01-01T00:00:00.000Z", @@ -1399,14 +1385,14 @@ describe("LoopsClient", () => { describe("updateEmailMessage", () => { it("should update an email message", async () => { const mockResponse = { - success: true, - emailMessageId: "msg_123", + id: "msg_123", campaignId: "camp_123", subject: "Updated subject", previewText: "Preview", fromName: "Loops", fromEmail: "hello", replyToEmail: "", + emailFormat: "styled", lmx: "", contentRevisionId: "rev_456", updatedAt: "2025-01-02T00:00:00.000Z", diff --git a/src/index.ts b/src/index.ts index 3c5ee23..21a3e91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -240,6 +240,8 @@ interface TransactionalEmailResource { draftEmailMessageId: string | null; /** The ID of the published email message, or `null` if none. */ publishedEmailMessageId: string | null; + /** The ID of the group this transactional email belongs to. */ + transactionalGroupId: string | null; /** The date the transactional email was created in ECMA-262 date-time format. */ createdAt: string; /** The date the transactional email was last updated in ECMA-262 date-time format. */ @@ -259,6 +261,135 @@ interface TransactionalDraftResponse extends TransactionalEmailResource { draftEmailMessageContentRevisionId: string | null; } +interface Group { + id: string; + name: string; + description: string; + createdAt: string; + updatedAt: string; +} + +interface ListGroupsResponse { + pagination: PaginationData; + data: Group[]; +} + +interface AudienceFilterBetweenValue { + from: string; + to: string; +} + +interface PropertyCondition { + type: "property"; + key: string; + operator: + | "any" + | "contains" + | "notContains" + | "equals" + | "notEquals" + | "greaterThan" + | "lessThan" + | "isTrue" + | "isFalse" + | "empty" + | "notEmpty" + | "dateEmpty" + | "dateNotEmpty" + | "after" + | "before" + | "between"; + value?: string | number | AudienceFilterBetweenValue; +} + +interface OptInCondition { + type: "optIn"; + status: "accepted" | "pending" | "rejected" | null; +} + +interface ActivityCondition { + type: "activity"; + action: "sent" | "opened" | "clicked"; + negate: boolean; + target: "campaign" | "workflow" | "workflowEmail"; + id: string; +} + +type AudienceFilterCondition = + | PropertyCondition + | OptInCondition + | ActivityCondition; + +interface AudienceFilter { + match: "all" | "any"; + conditions: AudienceFilterCondition[]; +} + +interface CampaignScheduling { + method: "now" | "schedule"; + /** ISO 8601 send time. Null when the method is `now`. */ + timestamp: string | null; +} + +interface CampaignSchedulingRequest { + method: "now" | "schedule"; + /** Required and must be in the future when `method` is `schedule`. */ + timestamp?: string; +} + +interface AudienceSegment { + id: string; + name: string; + description: string | null; + createdAt: string; + updatedAt: string; + filter: AudienceFilter | null; +} + +interface ListAudienceSegmentsResponse { + pagination: PaginationData; + data: AudienceSegment[]; +} + +interface WorkflowSummary { + id: string; + name: string; + createdAt: string; + updatedAt: string; +} + +interface ListWorkflowsResponse { + pagination: PaginationData; + data: WorkflowSummary[]; +} + +type SimplifiedWorkflowNode = { + typeName: string; + nextNodeIds: string[]; +} & Record; + +interface SimplifiedWorkflow { + id: string; + name?: string; + description?: string; + emoji?: string; + mailingListId?: string | null; + rootNodeId: string | null; + nodes: Record; +} + +type WorkflowNode = { + id: string; + workflowId: string; + typeName: string; + nextNodeIds: string[]; +} & Record; + +interface EmailMessagePreviewResponse { + /** The ID of the email message the preview was sent for. */ + id: string; +} + interface ListTransactionalsResourceResponse { pagination: PaginationData; data: TransactionalEmailResource[]; @@ -326,7 +457,7 @@ interface ThemeStyles { } interface Theme { - themeId: string; + id: string; name: string; styles: ThemeStyles; isDefault: boolean; @@ -335,76 +466,51 @@ interface Theme { } interface ListThemesResponse { - success: true; pagination: PaginationData; data: Theme[]; } -interface ThemeResponse { - success: true; - themeId: string; - name: string; - styles: ThemeStyles; - isDefault: boolean; - createdAt: string; - updatedAt: string; -} +type ThemeResponse = Theme; interface Component { - componentId: string; + id: string; name: string; lmx: string; } interface ListComponentsResponse { - success: true; pagination: PaginationData; data: Component[]; } -interface ComponentResponse { - success: true; - componentId: string; - name: string; - lmx: string; -} +type ComponentResponse = Component; -interface CampaignListItem { - campaignId: string; - emailMessageId: string | null; +interface Campaign { + id: string; name: string; - subject: string; status: string; createdAt: string; updatedAt: string; + emailMessageId: string | null; + campaignGroupId: string | null; + mailingListId: string | null; + audienceSegmentId: string | null; + audienceFilter: AudienceFilter | null; + scheduling: CampaignScheduling; } interface ListCampaignsResponse { - success: true; pagination: PaginationData; - data: CampaignListItem[]; + data: Campaign[]; } -interface CreateCampaignResponse { - success: true; - campaignId: string; - name: string; - status: string; - createdAt: string; - updatedAt: string; - emailMessageId: string; +interface CreateCampaignResponse extends Campaign { emailMessageContentRevisionId: string | null; } -interface CampaignResponse { - success: true; - campaignId: string; - name: string; - status: string; - createdAt: string; - updatedAt: string; - emailMessageId: string | null; -} +type CampaignResponse = Campaign; + +type CampaignListItem = Campaign; interface EmailMessageWarning { rule: string; @@ -414,17 +520,26 @@ interface EmailMessageWarning { } interface EmailMessageResponse { - success: true; - emailMessageId: string; - campaignId: string | null; + id: string; + /** Present only when the message belongs to a campaign. */ + campaignId?: string; + /** Present only when the message belongs to a transactional email. */ + transactionalId?: string; subject: string; previewText: string; fromName: string; fromEmail: string; replyToEmail: string; + ccEmail?: string; + bccEmail?: string; + languageCode?: string; + emailFormat: "styled" | "plain"; lmx: string; contentRevisionId: string | null; updatedAt: string; + contactPropertiesFallbacks?: Record; + eventPropertiesFallbacks?: Record; + dataVariablesFallbacks?: Record; warnings?: EmailMessageWarning[]; } @@ -993,17 +1108,15 @@ class LoopsClient { /** * Get a transactional email by ID. * - * @param {string} transactionalId The ID of the transactional email. + * @param {string} id The ID of the transactional email. * * @see https://loops.so/docs/api-reference/get-transactional-email * * @returns {Object} Transactional email (JSON) */ - async getTransactionalEmail( - transactionalId: string - ): Promise { + async getTransactionalEmail(id: string): Promise { return this._makeQuery({ - path: `v1/transactional-emails/${transactionalId}`, + path: `v1/transactional-emails/${id}`, }); } @@ -1012,6 +1125,7 @@ class LoopsClient { * * @param {Object} params * @param {string} params.name The name of the transactional email. + * @param {string} [params.transactionalGroupId] The ID of the group to add this transactional email to. * * @see https://loops.so/docs/api-reference/create-transactional-email * @@ -1019,52 +1133,70 @@ class LoopsClient { */ async createTransactionalEmail({ name, + transactionalGroupId, }: { name: string; + transactionalGroupId?: string; }): Promise { + const payload: { name: string; transactionalGroupId?: string } = { name }; + if (transactionalGroupId !== undefined) { + payload.transactionalGroupId = transactionalGroupId; + } return this._makeQuery({ path: "v1/transactional-emails", method: "POST", - payload: { name }, + payload, }); } /** * Update a transactional email. * - * @param {string} transactionalId The ID of the transactional email. + * @param {string} id The ID of the transactional email. * @param {Object} params - * @param {string} params.name The name of the transactional email. + * @param {string} [params.name] The name of the transactional email. + * @param {string} [params.transactionalGroupId] The ID of the group to move this transactional email to. * * @see https://loops.so/docs/api-reference/update-transactional-email * * @returns {Object} Updated transactional email (JSON) */ async updateTransactionalEmail( - transactionalId: string, - { name }: { name: string } + id: string, + { + name, + transactionalGroupId, + }: { + name?: string; + transactionalGroupId?: string; + } ): Promise { + const payload: { name?: string; transactionalGroupId?: string } = {}; + if (name !== undefined) payload.name = name; + if (transactionalGroupId !== undefined) { + payload.transactionalGroupId = transactionalGroupId; + } return this._makeQuery({ - path: `v1/transactional-emails/${transactionalId}`, + path: `v1/transactional-emails/${id}`, method: "POST", - payload: { name }, + payload, }); } /** * Ensure a transactional email has a draft email message. * - * @param {string} transactionalId The ID of the transactional email. + * @param {string} id The ID of the transactional email. * * @see https://loops.so/docs/api-reference/ensure-transactional-email-draft * * @returns {Object} Transactional email with draft (JSON) */ async ensureTransactionalEmailDraft( - transactionalId: string + id: string ): Promise { return this._makeQuery({ - path: `v1/transactional-emails/${transactionalId}/draft`, + path: `v1/transactional-emails/${id}/draft`, method: "POST", }); } @@ -1072,17 +1204,17 @@ class LoopsClient { /** * Publish a transactional email draft. * - * @param {string} transactionalId The ID of the transactional email. + * @param {string} id The ID of the transactional email. * * @see https://loops.so/docs/api-reference/publish-transactional-email * * @returns {Object} Published transactional email (JSON) */ async publishTransactionalEmail( - transactionalId: string + id: string ): Promise { return this._makeQuery({ - path: `v1/transactional-emails/${transactionalId}/publish`, + path: `v1/transactional-emails/${id}/publish`, method: "POST", }); } @@ -1100,6 +1232,49 @@ class LoopsClient { }); } + /** + * List audience segments. + * + * @param {Object} params + * @param {number} [params.perPage] How many results to return in each request. Must be between 10 and 50. Defaults to 20. + * @param {string} [params.cursor] A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. + * + * @see https://loops.so/docs/api-reference/list-audience-segments + * + * @returns {Object} List of audience segments (JSON) + */ + async listAudienceSegments({ + perPage, + cursor, + }: { + perPage?: number; + cursor?: string; + } = {}): Promise { + const params: { perPage: string; cursor?: string } = { + perPage: (perPage || 20).toString(), + }; + if (cursor) params["cursor"] = cursor; + return this._makeQuery({ + path: "v1/audience-segments", + params, + }); + } + + /** + * Get an audience segment by ID. + * + * @param {string} id The ID of the audience segment. + * + * @see https://loops.so/docs/api-reference/get-audience-segment + * + * @returns {Object} Audience segment (JSON) + */ + async getAudienceSegment(id: string): Promise { + return this._makeQuery({ + path: `v1/audience-segments/${id}`, + }); + } + /** * List email themes. * @@ -1131,15 +1306,15 @@ class LoopsClient { /** * Get a theme by ID. * - * @param {string} themeId The ID of the theme. + * @param {string} id The ID of the theme. * * @see https://loops.so/docs/api-reference/get-theme * * @returns {Object} Theme (JSON) */ - async getTheme(themeId: string): Promise { + async getTheme(id: string): Promise { return this._makeQuery({ - path: `v1/themes/${themeId}`, + path: `v1/themes/${id}`, }); } @@ -1174,15 +1349,15 @@ class LoopsClient { /** * Get a component by ID. * - * @param {string} componentId The ID of the component. + * @param {string} id The ID of the component. * * @see https://loops.so/docs/api-reference/get-component * * @returns {Object} Component (JSON) */ - async getComponent(componentId: string): Promise { + async getComponent(id: string): Promise { return this._makeQuery({ - path: `v1/components/${componentId}`, + path: `v1/components/${id}`, }); } @@ -1219,75 +1394,144 @@ class LoopsClient { * * @param {Object} params * @param {string} params.name The campaign name. + * @param {string} [params.campaignGroupId] The ID of the group to add this campaign to. + * @param {string | null} [params.mailingListId] The ID of the mailing list to send to. + * @param {string | null} [params.audienceSegmentId] The ID of an audience segment. + * @param {AudienceFilter | null} [params.audienceFilter] An audience filter. + * @param {CampaignSchedulingRequest} [params.scheduling] When the campaign should send. * * @see https://loops.so/docs/api-reference/create-campaign * * @returns {Object} Created campaign (JSON) */ - async createCampaign({ name }: { name: string }): Promise { + async createCampaign({ + name, + campaignGroupId, + mailingListId, + audienceSegmentId, + audienceFilter, + scheduling, + }: { + name: string; + campaignGroupId?: string; + mailingListId?: string | null; + audienceSegmentId?: string | null; + audienceFilter?: AudienceFilter | null; + scheduling?: CampaignSchedulingRequest; + }): Promise { + const payload: { + name: string; + campaignGroupId?: string; + mailingListId?: string | null; + audienceSegmentId?: string | null; + audienceFilter?: AudienceFilter | null; + scheduling?: CampaignSchedulingRequest; + } = { name }; + if (campaignGroupId !== undefined) payload.campaignGroupId = campaignGroupId; + if (mailingListId !== undefined) payload.mailingListId = mailingListId; + if (audienceSegmentId !== undefined) { + payload.audienceSegmentId = audienceSegmentId; + } + if (audienceFilter !== undefined) payload.audienceFilter = audienceFilter; + if (scheduling !== undefined) payload.scheduling = scheduling; return this._makeQuery({ path: "v1/campaigns", method: "POST", - payload: { name }, + payload, }); } /** * Get a campaign by ID. * - * @param {string} campaignId The ID of the campaign. + * @param {string} id The ID of the campaign. * * @see https://loops.so/docs/api-reference/get-campaign * * @returns {Object} Campaign (JSON) */ - async getCampaign(campaignId: string): Promise { + async getCampaign(id: string): Promise { return this._makeQuery({ - path: `v1/campaigns/${campaignId}`, + path: `v1/campaigns/${id}`, }); } /** - * Update a draft campaign's name. + * Update a draft campaign. * - * @param {string} campaignId The ID of the campaign. + * @param {string} id The ID of the campaign. * @param {Object} params - * @param {string} params.name The campaign name. + * @param {string} [params.name] The campaign name. + * @param {string} [params.campaignGroupId] The ID of the group to move this campaign to. + * @param {string | null} [params.mailingListId] The ID of the mailing list to send to. + * @param {string | null} [params.audienceSegmentId] The ID of an audience segment. + * @param {AudienceFilter | null} [params.audienceFilter] An audience filter. + * @param {CampaignSchedulingRequest} [params.scheduling] When the campaign should send. * * @see https://loops.so/docs/api-reference/update-campaign * * @returns {Object} Updated campaign (JSON) */ async updateCampaign( - campaignId: string, - { name }: { name: string } + id: string, + { + name, + campaignGroupId, + mailingListId, + audienceSegmentId, + audienceFilter, + scheduling, + }: { + name?: string; + campaignGroupId?: string; + mailingListId?: string | null; + audienceSegmentId?: string | null; + audienceFilter?: AudienceFilter | null; + scheduling?: CampaignSchedulingRequest; + } ): Promise { + const payload: { + name?: string; + campaignGroupId?: string; + mailingListId?: string | null; + audienceSegmentId?: string | null; + audienceFilter?: AudienceFilter | null; + scheduling?: CampaignSchedulingRequest; + } = {}; + if (name !== undefined) payload.name = name; + if (campaignGroupId !== undefined) payload.campaignGroupId = campaignGroupId; + if (mailingListId !== undefined) payload.mailingListId = mailingListId; + if (audienceSegmentId !== undefined) { + payload.audienceSegmentId = audienceSegmentId; + } + if (audienceFilter !== undefined) payload.audienceFilter = audienceFilter; + if (scheduling !== undefined) payload.scheduling = scheduling; return this._makeQuery({ - path: `v1/campaigns/${campaignId}`, + path: `v1/campaigns/${id}`, method: "POST", - payload: { name }, + payload, }); } /** * Get an email message by ID. * - * @param {string} emailMessageId The ID of the email message. + * @param {string} id The ID of the email message. * * @see https://loops.so/docs/api-reference/get-email-message * * @returns {Object} Email message (JSON) */ - async getEmailMessage(emailMessageId: string): Promise { + async getEmailMessage(id: string): Promise { return this._makeQuery({ - path: `v1/email-messages/${emailMessageId}`, + path: `v1/email-messages/${id}`, }); } /** * Update an email message. * - * @param {string} emailMessageId The ID of the email message. + * @param {string} id The ID of the email message. * @param {Object} params * @param {string} [params.expectedRevisionId] The `contentRevisionId` you last fetched. Used for optimistic concurrency. * @param {string} [params.subject] The email subject. @@ -1295,14 +1539,21 @@ class LoopsClient { * @param {string} [params.fromName] The sender name. * @param {string} [params.fromEmail] The sender username (without `@` or domain). * @param {string} [params.replyToEmail] Reply-to email. Must be empty or a valid email address. + * @param {string} [params.ccEmail] CC email address. + * @param {string} [params.bccEmail] BCC email address. + * @param {string} [params.languageCode] Language code for the email. + * @param {"styled" | "plain"} [params.emailFormat] The rendering format of the email. * @param {string} [params.lmx] The email body serialized as LMX. + * @param {Record} [params.contactPropertiesFallbacks] Fallback values for contact properties. + * @param {Record} [params.eventPropertiesFallbacks] Fallback values for event properties. + * @param {Record} [params.dataVariablesFallbacks] Fallback values for data variables. * * @see https://loops.so/docs/api-reference/update-email-message * * @returns {Object} Updated email message (JSON) */ async updateEmailMessage( - emailMessageId: string, + id: string, { expectedRevisionId, subject, @@ -1310,7 +1561,14 @@ class LoopsClient { fromName, fromEmail, replyToEmail, + ccEmail, + bccEmail, + languageCode, + emailFormat, lmx, + contactPropertiesFallbacks, + eventPropertiesFallbacks, + dataVariablesFallbacks, }: { expectedRevisionId?: string; subject?: string; @@ -1318,7 +1576,14 @@ class LoopsClient { fromName?: string; fromEmail?: string; replyToEmail?: string; + ccEmail?: string; + bccEmail?: string; + languageCode?: string; + emailFormat?: "styled" | "plain"; lmx?: string; + contactPropertiesFallbacks?: Record; + eventPropertiesFallbacks?: Record; + dataVariablesFallbacks?: Record; } ): Promise { const payload: { @@ -1328,7 +1593,14 @@ class LoopsClient { fromName?: string; fromEmail?: string; replyToEmail?: string; + ccEmail?: string; + bccEmail?: string; + languageCode?: string; + emailFormat?: "styled" | "plain"; lmx?: string; + contactPropertiesFallbacks?: Record; + eventPropertiesFallbacks?: Record; + dataVariablesFallbacks?: Record; } = {}; if (expectedRevisionId !== undefined) payload.expectedRevisionId = expectedRevisionId; @@ -1337,9 +1609,333 @@ class LoopsClient { if (fromName !== undefined) payload.fromName = fromName; if (fromEmail !== undefined) payload.fromEmail = fromEmail; if (replyToEmail !== undefined) payload.replyToEmail = replyToEmail; + if (ccEmail !== undefined) payload.ccEmail = ccEmail; + if (bccEmail !== undefined) payload.bccEmail = bccEmail; + if (languageCode !== undefined) payload.languageCode = languageCode; + if (emailFormat !== undefined) payload.emailFormat = emailFormat; if (lmx !== undefined) payload.lmx = lmx; + if (contactPropertiesFallbacks !== undefined) { + payload.contactPropertiesFallbacks = contactPropertiesFallbacks; + } + if (eventPropertiesFallbacks !== undefined) { + payload.eventPropertiesFallbacks = eventPropertiesFallbacks; + } + if (dataVariablesFallbacks !== undefined) { + payload.dataVariablesFallbacks = dataVariablesFallbacks; + } + return this._makeQuery({ + path: `v1/email-messages/${id}`, + method: "POST", + payload, + }); + } + + /** + * Send a preview of an email message. + * + * @param {string} id The ID of the email message. + * @param {Object} params + * @param {string[]} params.emails One or more addresses to send the preview to. + * @param {Record} [params.contactProperties] Contact property values to render. + * @param {Record} [params.eventProperties] Event property values to render. + * @param {Record} [params.dataVariables] Transactional data variables to render. + * + * @see https://loops.so/docs/api-reference/send-email-message-preview + * + * @returns {Object} Preview confirmation (JSON) + */ + async sendEmailMessagePreview( + id: string, + { + emails, + contactProperties, + eventProperties, + dataVariables, + }: { + emails: string[]; + contactProperties?: Record; + eventProperties?: Record; + dataVariables?: Record; + } + ): Promise { + const payload: { + emails: string[]; + contactProperties?: Record; + eventProperties?: Record; + dataVariables?: Record; + } = { emails }; + if (contactProperties !== undefined) { + payload.contactProperties = contactProperties; + } + if (eventProperties !== undefined) { + payload.eventProperties = eventProperties; + } + if (dataVariables !== undefined) payload.dataVariables = dataVariables; + return this._makeQuery({ + path: `v1/email-messages/${id}/preview`, + method: "POST", + payload, + }); + } + + /** + * List workflows. + * + * @param {Object} params + * @param {number} [params.perPage] How many results to return in each request. Must be between 10 and 50. Defaults to 20. + * @param {string} [params.cursor] A cursor, to return a specific page of results. + * + * @see https://loops.so/docs/api-reference/list-workflows + * + * @returns {Object} List of workflows (JSON) + */ + async listWorkflows({ + perPage, + cursor, + }: { + perPage?: number; + cursor?: string; + } = {}): Promise { + const params: { perPage: string; cursor?: string } = { + perPage: (perPage || 20).toString(), + }; + if (cursor) params["cursor"] = cursor; + return this._makeQuery({ + path: "v1/workflows", + params, + }); + } + + /** + * Get a workflow by ID. + * + * @param {string} id The ID of the workflow. + * + * @see https://loops.so/docs/api-reference/get-workflow + * + * @returns {Object} Workflow graph (JSON) + */ + async getWorkflow(id: string): Promise { + return this._makeQuery({ + path: `v1/workflows/${id}`, + }); + } + + /** + * Get a workflow node by ID. + * + * @param {string} workflowId The ID of the workflow. + * @param {string} nodeId The ID of the workflow node. + * + * @see https://loops.so/docs/api-reference/get-workflow-node + * + * @returns {Object} Workflow node (JSON) + */ + async getWorkflowNode(workflowId: string, nodeId: string): Promise { + return this._makeQuery({ + path: `v1/workflows/${workflowId}/nodes/${nodeId}`, + }); + } + + /** + * List campaign groups. + * + * @param {Object} params + * @param {number} [params.perPage] How many results to return in each request. Must be between 10 and 50. Defaults to 20. + * @param {string} [params.cursor] A cursor, to return a specific page of results. + * + * @see https://loops.so/docs/api-reference/list-campaign-groups + * + * @returns {Object} List of campaign groups (JSON) + */ + async listCampaignGroups({ + perPage, + cursor, + }: { + perPage?: number; + cursor?: string; + } = {}): Promise { + const params: { perPage: string; cursor?: string } = { + perPage: (perPage || 20).toString(), + }; + if (cursor) params["cursor"] = cursor; + return this._makeQuery({ + path: "v1/campaign-groups", + params, + }); + } + + /** + * Create a campaign group. + * + * @param {Object} params + * @param {string} params.name The group name. + * @param {string} [params.description] An optional description for the group. + * + * @see https://loops.so/docs/api-reference/create-campaign-group + * + * @returns {Object} Created campaign group (JSON) + */ + async createCampaignGroup({ + name, + description, + }: { + name: string; + description?: string; + }): Promise { + const payload: { name: string; description?: string } = { name }; + if (description !== undefined) payload.description = description; + return this._makeQuery({ + path: "v1/campaign-groups", + method: "POST", + payload, + }); + } + + /** + * Get a campaign group by ID. + * + * @param {string} id The ID of the campaign group. + * + * @see https://loops.so/docs/api-reference/get-campaign-group + * + * @returns {Object} Campaign group (JSON) + */ + async getCampaignGroup(id: string): Promise { + return this._makeQuery({ + path: `v1/campaign-groups/${id}`, + }); + } + + /** + * Update a campaign group. + * + * @param {string} id The ID of the campaign group. + * @param {Object} params + * @param {string} [params.name] The group name. + * @param {string} [params.description] A description for the group. + * + * @see https://loops.so/docs/api-reference/update-campaign-group + * + * @returns {Object} Updated campaign group (JSON) + */ + async updateCampaignGroup( + id: string, + { + name, + description, + }: { + name?: string; + description?: string; + } + ): Promise { + const payload: { name?: string; description?: string } = {}; + if (name !== undefined) payload.name = name; + if (description !== undefined) payload.description = description; + return this._makeQuery({ + path: `v1/campaign-groups/${id}`, + method: "POST", + payload, + }); + } + + /** + * List transactional groups. + * + * @param {Object} params + * @param {number} [params.perPage] How many results to return in each request. Must be between 10 and 50. Defaults to 20. + * @param {string} [params.cursor] A cursor, to return a specific page of results. + * + * @see https://loops.so/docs/api-reference/list-transactional-groups + * + * @returns {Object} List of transactional groups (JSON) + */ + async listTransactionalGroups({ + perPage, + cursor, + }: { + perPage?: number; + cursor?: string; + } = {}): Promise { + const params: { perPage: string; cursor?: string } = { + perPage: (perPage || 20).toString(), + }; + if (cursor) params["cursor"] = cursor; + return this._makeQuery({ + path: "v1/transactional-groups", + params, + }); + } + + /** + * Create a transactional group. + * + * @param {Object} params + * @param {string} params.name The group name. + * @param {string} [params.description] An optional description for the group. + * + * @see https://loops.so/docs/api-reference/create-transactional-group + * + * @returns {Object} Created transactional group (JSON) + */ + async createTransactionalGroup({ + name, + description, + }: { + name: string; + description?: string; + }): Promise { + const payload: { name: string; description?: string } = { name }; + if (description !== undefined) payload.description = description; + return this._makeQuery({ + path: "v1/transactional-groups", + method: "POST", + payload, + }); + } + + /** + * Get a transactional group by ID. + * + * @param {string} id The ID of the transactional group. + * + * @see https://loops.so/docs/api-reference/get-transactional-group + * + * @returns {Object} Transactional group (JSON) + */ + async getTransactionalGroup(id: string): Promise { + return this._makeQuery({ + path: `v1/transactional-groups/${id}`, + }); + } + + /** + * Update a transactional group. + * + * @param {string} id The ID of the transactional group. + * @param {Object} params + * @param {string} [params.name] The group name. + * @param {string} [params.description] A description for the group. + * + * @see https://loops.so/docs/api-reference/update-transactional-group + * + * @returns {Object} Updated transactional group (JSON) + */ + async updateTransactionalGroup( + id: string, + { + name, + description, + }: { + name?: string; + description?: string; + } + ): Promise { + const payload: { name?: string; description?: string } = {}; + if (name !== undefined) payload.name = name; + if (description !== undefined) payload.description = description; return this._makeQuery({ - path: `v1/email-messages/${emailMessageId}`, + path: `v1/transactional-groups/${id}`, method: "POST", payload, }); @@ -1427,6 +2023,25 @@ export { Component, ListComponentsResponse, ComponentResponse, + Group, + ListGroupsResponse, + AudienceFilterBetweenValue, + PropertyCondition, + OptInCondition, + ActivityCondition, + AudienceFilterCondition, + AudienceFilter, + CampaignScheduling, + CampaignSchedulingRequest, + AudienceSegment, + ListAudienceSegmentsResponse, + WorkflowSummary, + ListWorkflowsResponse, + SimplifiedWorkflowNode, + SimplifiedWorkflow, + WorkflowNode, + EmailMessagePreviewResponse, + Campaign, CampaignListItem, ListCampaignsResponse, CreateCampaignResponse, From 1e271a426fa95e4311f87fc40c8ab2d54c4eb45f Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 23 Jun 2026 14:52:41 +0300 Subject: [PATCH 2/4] Updated README --- README.md | 551 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 457 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 192fb3f..a9818e9 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,13 @@ You can use custom contact properties in API calls. Please make sure to [add cus - [updateTransactionalEmail()](#updatetransactionalemail) - [ensureTransactionalEmailDraft()](#ensuretransactionalemaildraft) - [publishTransactionalEmail()](#publishtransactionalemail) +- [listTransactionalGroups()](#listtransactionalgroups) +- [createTransactionalGroup()](#createtransactionalgroup) +- [getTransactionalGroup()](#gettransactionalgroup) +- [updateTransactionalGroup()](#updatetransactionalgroup) - [listDedicatedSendingIps()](#listdedicatedsendingips) +- [listAudienceSegments()](#listaudiencesegments) +- [getAudienceSegment()](#getaudiencesegment) - [listThemes()](#listthemes) - [getTheme()](#gettheme) - [listComponents()](#listcomponents) @@ -118,8 +124,16 @@ You can use custom contact properties in API calls. Please make sure to [add cus - [createCampaign()](#createcampaign) - [getCampaign()](#getcampaign) - [updateCampaign()](#updatecampaign) +- [listCampaignGroups()](#listcampaigngroups) +- [createCampaignGroup()](#createcampaigngroup) +- [getCampaignGroup()](#getcampaigngroup) +- [updateCampaignGroup()](#updatecampaigngroup) - [getEmailMessage()](#getemailmessage) - [updateEmailMessage()](#updateemailmessage) +- [sendEmailMessagePreview()](#sendemailmessagepreview) +- [listWorkflows()](#listworkflows) +- [getWorkflow()](#getworkflow) +- [getWorkflowNode()](#getworkflownode) - [createUpload()](#createupload) - [completeUpload()](#completeupload) @@ -881,6 +895,7 @@ const resp = await loops.listTransactionalEmails({ perPage: 15 }); "name": "Sign up confirmation", "draftEmailMessageId": null, "publishedEmailMessageId": "msg_123", + "transactionalGroupId": null, "createdAt": "2023-11-06T17:48:07.249Z", "updatedAt": "2023-11-06T17:48:07.249Z", "dataVariables": [] @@ -890,6 +905,7 @@ const resp = await loops.listTransactionalEmails({ perPage: 15 }); "name": "Password reset", "draftEmailMessageId": "msg_456", "publishedEmailMessageId": "msg_789", + "transactionalGroupId": "grp_123", "createdAt": "2025-02-02T02:56:28.845Z", "updatedAt": "2025-02-02T02:56:28.845Z", "dataVariables": [ @@ -911,9 +927,9 @@ Retrieve a single transactional email by ID. #### Parameters -| Name | Type | Required | Notes | -| ----------------- | ------ | -------- | ------------------------------------ | -| `transactionalId` | string | Yes | The ID of the transactional email. | +| Name | Type | Required | Notes | +| --------------------- | ------ | -------- | ------------------------------------ | +| `id` | string | Yes | The ID of the transactional email. | #### Example @@ -931,9 +947,10 @@ Create a new transactional email. An empty draft email message is created automa #### Parameters -| Name | Type | Required | Notes | -| ------ | ------ | -------- | ---------------------------------- | -| `name` | string | Yes | The name of the transactional email. | +| Name | Type | Required | Notes | +| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- | +| `name` | string | Yes | The name of the transactional email. | +| `transactionalGroupId`| string | No | The ID of the group to add this transactional email to. Defaults to the team's default group. | #### Example @@ -945,16 +962,17 @@ const resp = await loops.createTransactionalEmail({ name: "Welcome email" }); ### updateTransactionalEmail() -Update a transactional email by ID. +Update a transactional email by ID. At least one field is required. [API Reference](https://loops.so/docs/api-reference/update-transactional-email) #### Parameters -| Name | Type | Required | Notes | -| ----------------- | ------ | -------- | ---------------------------------- | -| `transactionalId` | string | Yes | The ID of the transactional email. | -| `name` | string | Yes | The name of the transactional email. | +| Name | Type | Required | Notes | +| --------------------- | ------ | -------- | -------------------------------------------------------- | +| `id` | string | Yes | The ID of the transactional email. | +| `name` | string | No | The name of the transactional email. | +| `transactionalGroupId`| string | No | The ID of the group to move this transactional email to. | #### Example @@ -962,6 +980,10 @@ Update a transactional email by ID. const resp = await loops.updateTransactionalEmail("trans_123", { name: "Updated name", }); + +const resp = await loops.updateTransactionalEmail("trans_123", { + transactionalGroupId: "grp_123", +}); ``` --- @@ -974,9 +996,9 @@ Ensure a transactional email has a draft email message. Use [`updateEmailMessage #### Parameters -| Name | Type | Required | Notes | -| ----------------- | ------ | -------- | ---------------------------------- | -| `transactionalId` | string | Yes | The ID of the transactional email. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | ---------------------------------- | +| `id` | string | Yes | The ID of the transactional email. | #### Example @@ -994,9 +1016,9 @@ Publish a transactional email's current draft. #### Parameters -| Name | Type | Required | Notes | -| ----------------- | ------ | -------- | ---------------------------------- | -| `transactionalId` | string | Yes | The ID of the transactional email. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | ---------------------------------- | +| `id` | string | Yes | The ID of the transactional email. | #### Example @@ -1037,8 +1059,7 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### listThemes() -Retrieve a paginated list of email themes, most recently created first. Requires the content API to be enabled for your team. - +Retrieve a paginated list of email themes, most recently created first. [API Reference](https://loops.so/docs/api-reference/list-themes) #### Parameters @@ -1060,7 +1081,6 @@ const resp = await loops.listThemes({ perPage: 15, cursor: "clyo0q4wo01p59fsecyx ```json { - "success": true, "pagination": { "totalResults": 1, "returnedResults": 1, @@ -1071,7 +1091,7 @@ const resp = await loops.listThemes({ perPage: 15, cursor: "clyo0q4wo01p59fsecyx }, "data": [ { - "themeId": "theme_123", + "id": "theme_123", "name": "Default", "styles": { "backgroundColor": "#ffffff" @@ -1090,15 +1110,14 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### getTheme() -Retrieve a single theme by ID. Requires the content API to be enabled for your team. - +Retrieve a single theme by ID. [API Reference](https://loops.so/docs/api-reference/get-theme) #### Parameters -| Name | Type | Required | Notes | -| --------- | ------ | -------- | -------------------- | -| `themeId` | string | Yes | The ID of the theme. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | -------------------- | +| `id` | string | Yes | The ID of the theme. | #### Example @@ -1110,8 +1129,7 @@ const resp = await loops.getTheme("theme_123"); ```json { - "success": true, - "themeId": "theme_123", + "id": "theme_123", "name": "Default", "styles": { "backgroundColor": "#ffffff" @@ -1128,8 +1146,7 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### listComponents() -Retrieve a paginated list of email components. Requires the content API to be enabled for your team. - +Retrieve a paginated list of email components. [API Reference](https://loops.so/docs/api-reference/list-components) #### Parameters @@ -1151,7 +1168,6 @@ const resp = await loops.listComponents({ perPage: 15 }); ```json { - "success": true, "pagination": { "totalResults": 1, "returnedResults": 1, @@ -1162,7 +1178,7 @@ const resp = await loops.listComponents({ perPage: 15 }); }, "data": [ { - "componentId": "comp_123", + "id": "comp_123", "name": "Header", "lmx": "
...
" } @@ -1176,15 +1192,14 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### getComponent() -Retrieve a single component by ID. Requires the content API to be enabled for your team. - +Retrieve a single component by ID. [API Reference](https://loops.so/docs/api-reference/get-component) #### Parameters -| Name | Type | Required | Notes | -| ------------- | ------ | -------- | ------------------------- | -| `componentId` | string | Yes | The ID of the component. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | ------------------------- | +| `id` | string | Yes | The ID of the component. | #### Example @@ -1196,8 +1211,7 @@ const resp = await loops.getComponent("comp_123"); ```json { - "success": true, - "componentId": "comp_123", + "id": "comp_123", "name": "Header", "lmx": "
...
" } @@ -1209,8 +1223,7 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### listCampaigns() -Retrieve a paginated list of campaigns. Requires the content API to be enabled for your team. - +Retrieve a paginated list of campaigns. [API Reference](https://loops.so/docs/api-reference/list-campaigns) #### Parameters @@ -1232,7 +1245,6 @@ const resp = await loops.listCampaigns({ perPage: 15 }); ```json { - "success": true, "pagination": { "totalResults": 1, "returnedResults": 1, @@ -1243,13 +1255,20 @@ const resp = await loops.listCampaigns({ perPage: 15 }); }, "data": [ { - "campaignId": "camp_123", + "id": "camp_123", "emailMessageId": "msg_123", "name": "Spring announcement", - "subject": "", "status": "Draft", "createdAt": "2025-01-01T00:00:00.000Z", - "updatedAt": "2025-01-01T00:00:00.000Z" + "updatedAt": "2025-01-01T00:00:00.000Z", + "campaignGroupId": null, + "mailingListId": null, + "audienceSegmentId": null, + "audienceFilter": null, + "scheduling": { + "method": "now", + "timestamp": null + } } ] } @@ -1261,34 +1280,51 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### createCampaign() -Create a new draft campaign. An empty email message is created automatically and its `emailMessageId` is returned. Use [`updateEmailMessage()`](#updateemailmessage) to set subject, sender, preview text, and LMX content. Requires the content API to be enabled for your team. - +Create a new draft campaign. An empty email message is created automatically and its `emailMessageId` is returned. Use [`updateEmailMessage()`](#updateemailmessage) to set subject, sender, preview text, and LMX content. The audience (mailing list, segment, or filter), group, and scheduling can be set on create or later via update. [API Reference](https://loops.so/docs/api-reference/create-campaign) #### Parameters -| Name | Type | Required | Notes | -| ------ | ------ | -------- | ------------------ | -| `name` | string | Yes | The campaign name. | +| Name | Type | Required | Notes | +| ------------------- | ------- | -------- | ---------------------------------------------------------------------------------------- | +| `name` | string | Yes | The campaign name. | +| `campaignGroupId` | string | No | The ID of the group to add this campaign to. | +| `mailingListId` | string | No | The ID of the mailing list to send to. | +| `audienceSegmentId` | string | No | The ID of an audience segment. Setting this clears any `audienceFilter`. | +| `audienceFilter` | object | No | An audience filter object. See the [API reference](https://loops.so/docs/api-reference/create-campaign). | +| `scheduling` | object | No | When the campaign should send (`method`: `now` or `schedule`, with optional `timestamp`). | #### Example ```javascript const resp = await loops.createCampaign({ name: "Spring announcement" }); + +const resp = await loops.createCampaign({ + name: "Spring announcement", + mailingListId: "list_123", + scheduling: { method: "schedule", timestamp: "2026-06-15T10:00:00.000Z" }, +}); ``` #### Response ```json { - "success": true, - "campaignId": "camp_123", + "id": "camp_123", "name": "Spring announcement", "status": "Draft", "createdAt": "2025-01-01T00:00:00.000Z", "updatedAt": "2025-01-01T00:00:00.000Z", "emailMessageId": "msg_123", - "emailMessageContentRevisionId": "rev_123" + "emailMessageContentRevisionId": "rev_123", + "campaignGroupId": null, + "mailingListId": null, + "audienceSegmentId": null, + "audienceFilter": null, + "scheduling": { + "method": "now", + "timestamp": null + } } ``` @@ -1298,15 +1334,14 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### getCampaign() -Retrieve a single campaign by ID. Requires the content API to be enabled for your team. - +Retrieve a single campaign by ID. [API Reference](https://loops.so/docs/api-reference/get-campaign) #### Parameters -| Name | Type | Required | Notes | -| ------------ | ------ | -------- | ----------------------- | -| `campaignId` | string | Yes | The ID of the campaign. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | ----------------------- | +| `id` | string | Yes | The ID of the campaign. | #### Example @@ -1318,13 +1353,20 @@ const resp = await loops.getCampaign("camp_123"); ```json { - "success": true, - "campaignId": "camp_123", + "id": "camp_123", "name": "Spring announcement", "status": "Draft", "createdAt": "2025-01-01T00:00:00.000Z", "updatedAt": "2025-01-01T00:00:00.000Z", - "emailMessageId": "msg_123" + "emailMessageId": "msg_123", + "campaignGroupId": null, + "mailingListId": null, + "audienceSegmentId": null, + "audienceFilter": null, + "scheduling": { + "method": "now", + "timestamp": null + } } ``` @@ -1334,16 +1376,20 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### updateCampaign() -Update a draft campaign's name. Campaigns can only be updated while in draft status. Requires the content API to be enabled for your team. - +Update a draft campaign's name, group, audience, or scheduling. At least one field must be provided. Campaigns can only be updated while in draft status. [API Reference](https://loops.so/docs/api-reference/update-campaign) #### Parameters -| Name | Type | Required | Notes | -| ------------ | ------ | -------- | ----------------------- | -| `campaignId` | string | Yes | The ID of the campaign. | -| `name` | string | Yes | The campaign name. | +| Name | Type | Required | Notes | +| ------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- | +| `id` | string | Yes | The ID of the campaign. | +| `name` | string | No | The campaign name. | +| `campaignGroupId` | string | No | The ID of the group to move this campaign to. | +| `mailingListId` | string | No | The ID of the mailing list to send to. | +| `audienceSegmentId` | string | No | The ID of an audience segment. Setting this clears any `audienceFilter`. | +| `audienceFilter` | object | No | An audience filter object. | +| `scheduling` | object | No | When the campaign should send. At least one field is required. | #### Example @@ -1355,13 +1401,20 @@ const resp = await loops.updateCampaign("camp_123", { name: "Updated name" }); ```json { - "success": true, - "campaignId": "camp_123", + "id": "camp_123", "name": "Updated name", "status": "Draft", "createdAt": "2025-01-01T00:00:00.000Z", "updatedAt": "2025-01-02T00:00:00.000Z", - "emailMessageId": "msg_123" + "emailMessageId": "msg_123", + "campaignGroupId": null, + "mailingListId": null, + "audienceSegmentId": null, + "audienceFilter": null, + "scheduling": { + "method": "now", + "timestamp": null + } } ``` @@ -1379,15 +1432,14 @@ HTTP 409 Conflict ### getEmailMessage() -Retrieve an email message, including its compiled LMX content. Requires the content API to be enabled for your team. - +Retrieve an email message, including its compiled LMX content. [API Reference](https://loops.so/docs/api-reference/get-email-message) #### Parameters -| Name | Type | Required | Notes | -| ----------------- | ------ | -------- | ---------------------------- | -| `emailMessageId` | string | Yes | The ID of the email message. | +| Name | Type | Required | Notes | +| ---- | ------ | -------- | ---------------------------- | +| `id` | string | Yes | The ID of the email message. | #### Example @@ -1399,14 +1451,14 @@ const resp = await loops.getEmailMessage("msg_123"); ```json { - "success": true, - "emailMessageId": "msg_123", + "id": "msg_123", "campaignId": "camp_123", "subject": "Hello", "previewText": "Preview text", "fromName": "Loops", "fromEmail": "hello", "replyToEmail": "", + "emailFormat": "styled", "lmx": "...", "contentRevisionId": "rev_123", "updatedAt": "2025-01-01T00:00:00.000Z" @@ -1419,22 +1471,28 @@ Error handling is done through the `APIError` class, which provides `statusCode` ### updateEmailMessage() -Update fields on an email message (subject, preview text, sender, LMX content). The campaign must be in draft status. Supply `expectedRevisionId` matching the current `contentRevisionId` — the server rejects mismatched revisions with 409. Requires the content API to be enabled for your team. - +Update fields on an email message (subject, preview text, sender, LMX content). The campaign must be in draft status. Supply `expectedRevisionId` matching the current `contentRevisionId` — the server rejects mismatched revisions with 409. [API Reference](https://loops.so/docs/api-reference/update-email-message) #### Parameters -| Name | Type | Required | Notes | -| ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `emailMessageId` | string | Yes | The ID of the email message. | -| `expectedRevisionId` | string | No | The `contentRevisionId` you last fetched. Used for optimistic concurrency. | -| `subject` | string | No | The email subject. | -| `previewText` | string | No | The email preview text. | -| `fromName` | string | No | The sender name. | -| `fromEmail` | string | No | The sender username (without `@` or domain). The team's sending domain is appended automatically. | -| `replyToEmail` | string | No | Reply-to email. Must be empty or a valid email address. | -| `lmx` | string | No | The email body serialized as LMX. Styles must be embedded in the LMX `