diff --git a/README.md b/README.md
index 192fb3f..ca267c5 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)
@@ -200,7 +214,7 @@ const resp = await loops.createContact({
```json
{
"success": true,
- "id": "id_of_contact"
+ "id": "clw9h3y5a014yl70k9m2n4p8q"
}
```
@@ -264,7 +278,7 @@ const resp = await loops.updateContact({
```json
{
"success": true,
- "id": "id_of_contact"
+ "id": "clw9h3y5a014yl70k9m2n4p8q"
}
```
@@ -880,7 +894,8 @@ const resp = await loops.listTransactionalEmails({ perPage: 15 });
"id": "clfn0k1yg001imo0fdeqg30i8",
"name": "Sign up confirmation",
"draftEmailMessageId": null,
- "publishedEmailMessageId": "msg_123",
+ "publishedEmailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
+ "transactionalGroupId": null,
"createdAt": "2023-11-06T17:48:07.249Z",
"updatedAt": "2023-11-06T17:48:07.249Z",
"dataVariables": []
@@ -888,8 +903,9 @@ const resp = await loops.listTransactionalEmails({ perPage: 15 });
{
"id": "cll42l54f20i1la0lfooe3z12",
"name": "Password reset",
- "draftEmailMessageId": "msg_456",
- "publishedEmailMessageId": "msg_789",
+ "draftEmailMessageId": "clm8k2n4p000yl70f6g7h8i9j",
+ "publishedEmailMessageId": "clm8k2n4p001yl70k1l2m3n4o",
+ "transactionalGroupId": "clq3b7s9u006yl70p5q6r7s8t",
"createdAt": "2025-02-02T02:56:28.845Z",
"updatedAt": "2025-02-02T02:56:28.845Z",
"dataVariables": [
@@ -911,14 +927,14 @@ 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 |
+| --------------------- | ------ | -------- | ------------------------------------ |
+| `transactionalId` | string | Yes | The ID of the transactional email. |
#### Example
```javascript
-const resp = await loops.getTransactionalEmail("trans_123");
+const resp = await loops.getTransactionalEmail("clfn0k1yg001imo0fdeqg30i8");
```
---
@@ -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,23 +962,28 @@ 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 |
+| --------------------- | ------ | -------- | -------------------------------------------------------- |
+| `transactionalId` | 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
```javascript
-const resp = await loops.updateTransactionalEmail("trans_123", {
+const resp = await loops.updateTransactionalEmail("clfn0k1yg001imo0fdeqg30i8", {
name: "Updated name",
});
+
+const resp = await loops.updateTransactionalEmail("clfn0k1yg001imo0fdeqg30i8", {
+ transactionalGroupId: "clq3b7s9u006yl70p5q6r7s8t",
+});
```
---
@@ -974,14 +996,14 @@ Ensure a transactional email has a draft email message. Use [`updateEmailMessage
#### Parameters
-| Name | Type | Required | Notes |
-| ----------------- | ------ | -------- | ---------------------------------- |
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ---------------------------------- |
| `transactionalId` | string | Yes | The ID of the transactional email. |
#### Example
```javascript
-const resp = await loops.ensureTransactionalEmailDraft("trans_123");
+const resp = await loops.ensureTransactionalEmailDraft("clfn0k1yg001imo0fdeqg30i8");
```
---
@@ -994,14 +1016,14 @@ Publish a transactional email's current draft.
#### Parameters
-| Name | Type | Required | Notes |
-| ----------------- | ------ | -------- | ---------------------------------- |
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ---------------------------------- |
| `transactionalId` | string | Yes | The ID of the transactional email. |
#### Example
```javascript
-const resp = await loops.publishTransactionalEmail("trans_123");
+const resp = await loops.publishTransactionalEmail("clfn0k1yg001imo0fdeqg30i8");
```
---
@@ -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": "clo1z5q7s004yl70y3z4a5b6c",
"name": "Default",
"styles": {
"backgroundColor": "#ffffff"
@@ -1090,28 +1110,26 @@ 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 |
-| --------- | ------ | -------- | -------------------- |
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | -------------------- |
| `themeId` | string | Yes | The ID of the theme. |
#### Example
```javascript
-const resp = await loops.getTheme("theme_123");
+const resp = await loops.getTheme("clo1z5q7s004yl70y3z4a5b6c");
```
#### Response
```json
{
- "success": true,
- "themeId": "theme_123",
+ "id": "clo1z5q7s004yl70y3z4a5b6c",
"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": "clp2a6r8t005yl70d7e8f9g0h",
"name": "Header",
"lmx": ""
}
@@ -1176,28 +1192,26 @@ 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 |
-| ------------- | ------ | -------- | ------------------------- |
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ------------------------- |
| `componentId` | string | Yes | The ID of the component. |
#### Example
```javascript
-const resp = await loops.getComponent("comp_123");
+const resp = await loops.getComponent("clp2a6r8t005yl70d7e8f9g0h");
```
#### Response
```json
{
- "success": true,
- "componentId": "comp_123",
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
"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",
- "emailMessageId": "msg_123",
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
"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: "cm06f5v0e45nf0ml5754o9cix",
+ scheduling: { method: "schedule", timestamp: "2026-06-15T10:00:00.000Z" },
+});
```
#### Response
```json
{
- "success": true,
- "campaignId": "camp_123",
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
"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"
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
+ "emailMessageContentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
+ "campaignGroupId": null,
+ "mailingListId": null,
+ "audienceSegmentId": null,
+ "audienceFilter": null,
+ "scheduling": {
+ "method": "now",
+ "timestamp": null
+ }
}
```
@@ -1298,33 +1334,39 @@ 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 |
-| ------------ | ------ | -------- | ----------------------- |
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ----------------------- |
| `campaignId` | string | Yes | The ID of the campaign. |
#### Example
```javascript
-const resp = await loops.getCampaign("camp_123");
+const resp = await loops.getCampaign("cln0y4p6r003yl70i1j2k3l4m");
```
#### Response
```json
{
- "success": true,
- "campaignId": "camp_123",
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
"name": "Spring announcement",
"status": "Draft",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
- "emailMessageId": "msg_123"
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
+ "campaignGroupId": null,
+ "mailingListId": null,
+ "audienceSegmentId": null,
+ "audienceFilter": null,
+ "scheduling": {
+ "method": "now",
+ "timestamp": null
+ }
}
```
@@ -1334,34 +1376,45 @@ 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 |
+| ------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
+| `campaignId` | 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
```javascript
-const resp = await loops.updateCampaign("camp_123", { name: "Updated name" });
+const resp = await loops.updateCampaign("cln0y4p6r003yl70i1j2k3l4m", { name: "Updated name" });
```
#### Response
```json
{
- "success": true,
- "campaignId": "camp_123",
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
"name": "Updated name",
"status": "Draft",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-02T00:00:00.000Z",
- "emailMessageId": "msg_123"
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
+ "campaignGroupId": null,
+ "mailingListId": null,
+ "audienceSegmentId": null,
+ "audienceFilter": null,
+ "scheduling": {
+ "method": "now",
+ "timestamp": null
+ }
}
```
@@ -1379,36 +1432,35 @@ 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 |
+| ---- | ------ | -------- | ---------------------------- |
+| `emailMessageId` | string | Yes | The ID of the email message. |
#### Example
```javascript
-const resp = await loops.getEmailMessage("msg_123");
+const resp = await loops.getEmailMessage("clm9x3o5q002yl70a8b3c4d5e");
```
#### Response
```json
{
- "success": true,
- "emailMessageId": "msg_123",
- "campaignId": "camp_123",
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
"subject": "Hello",
"previewText": "Preview text",
"fromName": "Loops",
"fromEmail": "hello",
"replyToEmail": "",
+ "emailFormat": "styled",
"lmx": "...",
- "contentRevisionId": "rev_123",
+ "contentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
```
@@ -1419,28 +1471,34 @@ 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 `` tag. |
+| 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. |
+| `ccEmail` | string | No | CC email address. Requires CC/BCC to be enabled for your team. |
+| `bccEmail` | string | No | BCC email address. Requires CC/BCC to be enabled for your team. |
+| `languageCode` | string | No | Language code for the email. Requires translation to be enabled for your team. |
+| `emailFormat` | string | No | The rendering format: `styled` or `plain`. |
+| `lmx` | string | No | The email body serialized as LMX. Styles must be embedded in the LMX `` tag. |
+| `contactPropertiesFallbacks` | object | No | Fallback values for contact properties, keyed by property name. |
+| `eventPropertiesFallbacks` | object | No | Fallback values for event properties, keyed by property name. |
+| `dataVariablesFallbacks` | object | No | Fallback values for data variables, keyed by variable name. |
#### Example
```javascript
-const resp = await loops.updateEmailMessage("msg_123", {
- expectedRevisionId: "rev_123",
+const resp = await loops.updateEmailMessage("clm9x3o5q002yl70a8b3c4d5e", {
+ expectedRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
subject: "Hello",
previewText: "Preview text",
fromName: "Loops",
@@ -1453,16 +1511,16 @@ const resp = await loops.updateEmailMessage("msg_123", {
```json
{
- "success": true,
- "emailMessageId": "msg_123",
- "campaignId": "camp_123",
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
"subject": "Hello",
"previewText": "Preview text",
"fromName": "Loops",
"fromEmail": "hello",
"replyToEmail": "",
+ "emailFormat": "styled",
"lmx": "...",
- "contentRevisionId": "rev_456",
+ "contentRevisionId": "clv8g2x4z013yl70s9t0u1v2w",
"updatedAt": "2025-01-02T00:00:00.000Z",
"warnings": [
{
@@ -1486,6 +1544,301 @@ HTTP 409 Conflict
---
+### listTransactionalGroups()
+
+Retrieve a paginated list of transactional groups.
+[API Reference](https://loops.so/docs/api-reference/list-transactional-groups)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
+| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
+
+#### Example
+
+```javascript
+const resp = await loops.listTransactionalGroups();
+```
+
+---
+
+### createTransactionalGroup()
+
+Create a new transactional group.
+[API Reference](https://loops.so/docs/api-reference/create-transactional-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------- | ------ | -------- | -------------------------------------- |
+| `name` | string | Yes | The group name. Cannot be `"Unsorted"`. |
+| `description` | string | No | An optional description for the group. |
+
+#### Example
+
+```javascript
+const resp = await loops.createTransactionalGroup({
+ name: "Onboarding",
+ description: "Transactional emails for new users",
+});
+```
+
+---
+
+### getTransactionalGroup()
+
+Retrieve a single transactional group by ID.
+[API Reference](https://loops.so/docs/api-reference/get-transactional-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ---------------------------------- |
+| `transactionalGroupId` | string | Yes | The ID of the transactional group. |
+
+#### Example
+
+```javascript
+const resp = await loops.getTransactionalGroup("clq3b7s9u006yl70p5q6r7s8t");
+```
+
+---
+
+### updateTransactionalGroup()
+
+Update a transactional group's name or description. At least one field must be provided. The reserved `"Unsorted"` group cannot be edited.
+[API Reference](https://loops.so/docs/api-reference/update-transactional-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------- | ------ | -------- | ---------------------------------- |
+| `transactionalGroupId` | string | Yes | The ID of the transactional group. |
+| `name` | string | No | The group name. |
+| `description` | string | No | A description for the group. |
+
+#### Example
+
+```javascript
+const resp = await loops.updateTransactionalGroup("clq3b7s9u006yl70p5q6r7s8t", {
+ name: "Updated name",
+});
+```
+
+---
+
+### listAudienceSegments()
+
+Retrieve a paginated list of audience segments.
+[API Reference](https://loops.so/docs/api-reference/list-audience-segments)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
+| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
+
+#### Example
+
+```javascript
+const resp = await loops.listAudienceSegments();
+```
+
+---
+
+### getAudienceSegment()
+
+Retrieve a single audience segment by ID.
+[API Reference](https://loops.so/docs/api-reference/get-audience-segment)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | -------------------------------- |
+| `audienceSegmentId` | string | Yes | The ID of the audience segment. |
+
+#### Example
+
+```javascript
+const resp = await loops.getAudienceSegment("clr4c8t0v008yl70x3y4z5a6b");
+```
+
+---
+
+### listCampaignGroups()
+
+Retrieve a paginated list of campaign groups.
+[API Reference](https://loops.so/docs/api-reference/list-campaign-groups)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
+| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
+
+#### Example
+
+```javascript
+const resp = await loops.listCampaignGroups();
+```
+
+---
+
+### createCampaignGroup()
+
+Create a new campaign group.
+[API Reference](https://loops.so/docs/api-reference/create-campaign-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------- | ------ | -------- | -------------------------------------- |
+| `name` | string | Yes | The group name. Cannot be `"Unsorted"`. |
+| `description` | string | No | An optional description for the group. |
+
+#### Example
+
+```javascript
+const resp = await loops.createCampaignGroup({
+ name: "Newsletters",
+});
+```
+
+---
+
+### getCampaignGroup()
+
+Retrieve a single campaign group by ID.
+[API Reference](https://loops.so/docs/api-reference/get-campaign-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ----------------------------- |
+| `campaignGroupId` | string | Yes | The ID of the campaign group. |
+
+#### Example
+
+```javascript
+const resp = await loops.getCampaignGroup("clq3b7s9u007yl70u9v0w1x2y");
+```
+
+---
+
+### updateCampaignGroup()
+
+Update a campaign group's name or description. At least one field must be provided. The reserved `"Unsorted"` group cannot be edited.
+[API Reference](https://loops.so/docs/api-reference/update-campaign-group)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------- | ------ | -------- | ----------------------------- |
+| `campaignGroupId` | string | Yes | The ID of the campaign group. |
+| `name` | string | No | The group name. |
+| `description` | string | No | A description for the group. |
+
+#### Example
+
+```javascript
+const resp = await loops.updateCampaignGroup("clq3b7s9u007yl70u9v0w1x2y", {
+ description: "Monthly product updates",
+});
+```
+
+---
+
+### sendEmailMessagePreview()
+
+Send a test preview of an email message to one or more addresses.
+[API Reference](https://loops.so/docs/api-reference/send-email-message-preview)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------------- | -------- | -------- | ------------------------------------------------------------------------------------------- |
+| `emailMessageId` | string | Yes | The ID of the email message. |
+| `emails` | string[] | Yes | One or more addresses to send the preview to. |
+| `contactProperties` | object | No | Contact property values to render. Accepted for campaign and workflow previews. |
+| `eventProperties` | object | No | Event property values to render. Accepted for workflow previews only. |
+| `dataVariables` | object | No | Transactional data variables to render. Accepted for transactional previews only. |
+
+#### Example
+
+```javascript
+const resp = await loops.sendEmailMessagePreview("clm9x3o5q002yl70a8b3c4d5e", {
+ emails: ["test@example.com"],
+ contactProperties: {
+ firstName: "Alex",
+ },
+});
+```
+
+---
+
+### listWorkflows()
+
+Retrieve a paginated list of workflows.
+[API Reference](https://loops.so/docs/api-reference/list-workflows)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
+| `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
+
+#### Example
+
+```javascript
+const resp = await loops.listWorkflows();
+```
+
+---
+
+### getWorkflow()
+
+Retrieve a workflow graph with node type names, connections, and selected display fields.
+[API Reference](https://loops.so/docs/api-reference/get-workflow)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ---- | ------ | -------- | ------------------------ |
+| `workflowId` | string | Yes | The ID of the workflow. |
+
+#### Example
+
+```javascript
+const resp = await loops.getWorkflow("cls5d9u1w009yl70c7d8e9f0g");
+```
+
+---
+
+### getWorkflowNode()
+
+Retrieve detailed data for a single workflow node.
+[API Reference](https://loops.so/docs/api-reference/get-workflow-node)
+
+#### Parameters
+
+| Name | Type | Required | Notes |
+| ------------ | ------ | -------- | ---------------------------- |
+| `workflowId` | string | Yes | The ID of the workflow. |
+| `nodeId` | string | Yes | The ID of the workflow node. |
+
+#### Example
+
+```javascript
+const resp = await loops.getWorkflowNode("cls5d9u1w009yl70c7d8e9f0g", "clt6e0v2x010yl70h1i2j3k4l");
+```
+
+---
+
### createUpload()
Request a pre-signed URL to upload an image asset. Upload the file with an HTTP `PUT` to the returned `presignedUrl`, then call [`completeUpload()`](#completeupload).
@@ -1525,18 +1878,27 @@ Finalize an asset after the file has been uploaded to the pre-signed URL.
#### Example
```javascript
-const resp = await loops.completeUpload("asset_123");
+const resp = await loops.completeUpload("clu7f1w3y011yl70m5n6o7p8q");
```
---
## Version history
-- `v7.0.0` (Jun 8, 2026)
- - Added transactional email management ([`getTransactionalEmail()`](#gettransactionalemail), [`createTransactionalEmail()`](#createtransactionalemail), [`updateTransactionalEmail()`](#updatetransactionalemail), [`ensureTransactionalEmailDraft()`](#ensuretransactionalemaildraft), [`publishTransactionalEmail()`](#publishtransactionalemail))
+- `v7.0.0` (Jun 24, 2026)
+ - Added transactional email management ([`getTransactionalEmail()`](#gettransactionalemail), [`createTransactionalEmail()`](#createtransactionalemail), [`updateTransactionalEmail()`](#updatetransactionalemail), [`ensureTransactionalEmailDraft()`](#ensuretransactionalemaildraft), [`publishTransactionalEmail()`](#publishtransactionalemail)).
+ - Added workflows ([`listWorkflows()`](#listworkflows), [`getWorkflow()`](#getworkflow), [`getWorkflowNode()`](#getworkflownode)).
- Added image uploads ([`createUpload()`](#createupload), [`completeUpload()`](#completeupload)).
+ - Added audience segments ([`listAudienceSegments()`](#listaudiencesegments), [`getAudienceSegment()`](#getaudiencesegment)).
+ - Added campaign groups ([`listCampaignGroups()`](#listcampaigngroups), [`createCampaignGroup()`](#createcampaigngroup), [`getCampaignGroup()`](#getcampaigngroup), [`updateCampaignGroup()`](#updatecampaigngroup)).
+ - Added transactional groups ([`listTransactionalGroups()`](#listtransactionalgroups), [`createTransactionalGroup()`](#createtransactionalgroup), [`getTransactionalGroup()`](#gettransactionalgroup), [`updateTransactionalGroup()`](#updatetransactionalgroup)).
+ - Added [`sendEmailMessagePreview()`](#sendemailmessagepreview).
+ - Expanded [`createCampaign()`](#createcampaign) and [`updateCampaign()`](#updatecampaign) with group, audience, and scheduling options.
+ - Expanded [`createTransactionalEmail()`](#createtransactionalemail) and [`updateTransactionalEmail()`](#updatetransactionalemail) with `transactionalGroupId`.
+ - Expanded [`updateEmailMessage()`](#updateemailmessage) with `ccEmail`, `bccEmail`, `languageCode`, `emailFormat`, and property fallback maps.
- Renamed list methods to use a consistent `list` naming pattern (breaking change): [`listContactProperties()`](#listcontactproperties), [`listMailingLists()`](#listmailinglists), [`listTransactionalEmails()`](#listtransactionalemails), [`listDedicatedSendingIps()`](#listdedicatedsendingips), [`listThemes()`](#listthemes), [`listComponents()`](#listcomponents), and [`listCampaigns()`](#listcampaigns). Note `getCustomProperties` is now `listContactProperties`.
- - Note: [`listTransactionalEmails()`](#listtransactionalemails) uses a new underlying API endpoint returns an updated response shape.
+ - [`listTransactionalEmails()`](#listtransactionalemails) uses a new underlying API endpoint and returns an updated response shape (including `transactionalGroupId`).
+ - Breaking change: API response objects now use `id` instead of `campaignId`, `themeId`, `componentId`, and `emailMessageId`. List and detail responses no longer include a top-level `success` field.
- `v6.4.0` (May 18, 2026) - Added `getDedicatedSendingIps()`, and endpoints for creating and editing campaings (`getThemes()`, [`getTheme()`](#gettheme), `getComponents()`, [`getComponent()`](#getcomponent), `getCampaigns()`, [`createCampaign()`](#createcampaign), [`getCampaign()`](#getcampaign), [`updateCampaign()`](#updatecampaign), [`getEmailMessage()`](#getemailmessage), and [`updateEmailMessage()`](#updateemailmessage)).
- `v6.3.0` (Apr 8, 2026) - Added [`checkContactSuppression()`](#checkcontactsuppression) and [`removeContactSuppression()`](#removecontactsuppression) methods.
- `v6.2.0` (Feb 9, 2026) - Support for the new arrays feature in sendTransactionalEmail.
diff --git a/src/__tests__/LoopsClient.test.ts b/src/__tests__/LoopsClient.test.ts
index 477c509..e16d13d 100644
--- a/src/__tests__/LoopsClient.test.ts
+++ b/src/__tests__/LoopsClient.test.ts
@@ -76,7 +76,7 @@ describe("LoopsClient", () => {
describe("createContact", () => {
it("should create a contact with the provided email", async () => {
const email = "test@example.com";
- const mockResponse = { success: true, id: "123" };
+ const mockResponse = { success: true, id: "clw9h3y5a014yl70k9m2n4p8q" };
global.fetch = jest.fn().mockResolvedValue({
ok: true,
@@ -102,7 +102,7 @@ describe("LoopsClient", () => {
age: 30,
isActive: true,
};
- const mockResponse = { success: true, id: "123" };
+ const mockResponse = { success: true, id: "clw9h3y5a014yl70k9m2n4p8q" };
global.fetch = jest.fn().mockResolvedValue({
ok: true,
@@ -159,9 +159,9 @@ describe("LoopsClient", () => {
userGroup: "customers",
};
const mailingLists = {
- newsletter_id: true,
+ cm06f5v0e45nf0ml5754o9cix: true,
};
- const mockResponse = { success: true, id: "123" };
+ const mockResponse = { success: true, id: "clw9h3y5a014yl70k9m2n4p8q" };
global.fetch = jest.fn().mockResolvedValue({
ok: true,
@@ -185,16 +185,16 @@ describe("LoopsClient", () => {
});
it("should update contact by userId", async () => {
- const userId = "user_123";
+ const userId = "1234567890";
const properties = {
firstName: "John",
lastName: "Doe",
userGroup: "customers",
};
const mailingLists = {
- newsletter_id: true,
+ cm06f5v0e45nf0ml5754o9cix: true,
};
- const mockResponse = { success: true, id: "123" };
+ const mockResponse = { success: true, id: "clw9h3y5a014yl70k9m2n4p8q" };
global.fetch = jest.fn().mockResolvedValue({
ok: true,
@@ -221,7 +221,7 @@ describe("LoopsClient", () => {
const email = "nonexistent@example.com";
const mockResponse = {
success: true,
- id: "123",
+ id: "clw9h3y5a014yl70k9m2n4p8q",
};
global.fetch = jest.fn().mockResolvedValue({
@@ -280,7 +280,7 @@ describe("LoopsClient", () => {
});
it("should check suppression status by userId", async () => {
- const userId = "user_123";
+ const userId = "1234567890";
const mockResponse = {
contact: {
id: "cll6b3i8901a9jx0oyktl2m4u",
@@ -303,7 +303,7 @@ describe("LoopsClient", () => {
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/contacts/suppression?userId=user_123"),
+ expect.stringContaining("v1/contacts/suppression?userId=1234567890"),
expect.objectContaining({
method: "GET",
})
@@ -314,7 +314,7 @@ describe("LoopsClient", () => {
await expect(
client.checkContactSuppression({
email: "test@example.com",
- userId: "user_123",
+ userId: "1234567890",
})
).rejects.toThrow(ValidationError);
});
@@ -355,7 +355,7 @@ describe("LoopsClient", () => {
});
it("should remove suppression by userId", async () => {
- const userId = "user_123";
+ const userId = "1234567890";
const mockResponse = {
success: true,
message: "User removed from suppression list.",
@@ -374,7 +374,7 @@ describe("LoopsClient", () => {
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/contacts/suppression?userId=user_123"),
+ expect.stringContaining("v1/contacts/suppression?userId=1234567890"),
expect.objectContaining({
method: "DELETE",
})
@@ -385,7 +385,7 @@ describe("LoopsClient", () => {
await expect(
client.removeContactSuppression({
email: "test@example.com",
- userId: "user_123",
+ userId: "1234567890",
})
).rejects.toThrow(ValidationError);
});
@@ -484,7 +484,7 @@ describe("LoopsClient", () => {
source: "web",
},
mailingLists: {
- newsletter_id: true,
+ cm06f5v0e45nf0ml5754o9cix: true,
},
};
const mockResponse = { success: true };
@@ -514,7 +514,7 @@ describe("LoopsClient", () => {
it("should send an event successfully with userId", async () => {
const eventData = {
- userId: "user_123",
+ userId: "1234567890",
eventName: "test_event",
};
const mockResponse = { success: true };
@@ -583,7 +583,7 @@ describe("LoopsClient", () => {
email: "test@example.com",
eventName: "test_event",
headers: {
- "Idempotency-Key": "unique_key_123",
+ "Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
},
};
const mockResponse = { success: true };
@@ -603,7 +603,9 @@ describe("LoopsClient", () => {
// Verify headers using Headers object methods
const headers = requestOptions.headers;
- expect(headers.get("Idempotency-Key")).toBe("unique_key_123");
+ expect(headers.get("Idempotency-Key")).toBe(
+ "550e8400-e29b-41d4-a716-446655440000"
+ );
// Verify the body doesn't contain idempotency key
expect(requestOptions.body).toBe(
@@ -645,7 +647,7 @@ describe("LoopsClient", () => {
describe("sendTransactionalEmail", () => {
it("should send a transactional email successfully", async () => {
const emailData = {
- transactionalId: "email_123",
+ transactionalId: "clfq6dinn000yl70fgwwyp82l",
email: "test@example.com",
dataVariables: {
name: "John",
@@ -676,7 +678,7 @@ describe("LoopsClient", () => {
it("should handle error when sending transactional email fails", async () => {
const emailData = {
- transactionalId: "invalid_id",
+ transactionalId: "clx0i4z6b015yl70r9s0t1u2v",
email: "test@example.com",
};
const mockResponse = {
@@ -814,10 +816,10 @@ describe("LoopsClient", () => {
it("should list transactional emails successfully", async () => {
const mockTransactionalEmails = [
{
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Welcome Email",
- draftEmailMessageId: "msg_draft_123",
- publishedEmailMessageId: "msg_pub_123",
+ draftEmailMessageId: "clm8k2n4p000yl70f6g7h8i9j",
+ publishedEmailMessageId: "clm8k2n4p001yl70k1l2m3n4o",
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-02T00:00:00.000Z",
dataVariables: ["name", "product"],
@@ -893,10 +895,11 @@ describe("LoopsClient", () => {
describe("getTransactionalEmail", () => {
it("should get a transactional email by ID", async () => {
const mockResponse = {
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Welcome Email",
draftEmailMessageId: null,
- publishedEmailMessageId: "msg_pub_123",
+ publishedEmailMessageId: "clm8k2n4p001yl70k1l2m3n4o",
+ transactionalGroupId: null,
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-02T00:00:00.000Z",
dataVariables: ["name"],
@@ -907,11 +910,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.getTransactionalEmail("trans_123");
+ const result = await client.getTransactionalEmail("clfn0k1yg001imo0fdeqg30i8");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/transactional-emails/trans_123"),
+ expect.stringContaining("v1/transactional-emails/clfn0k1yg001imo0fdeqg30i8"),
expect.objectContaining({ method: "GET" })
);
});
@@ -920,11 +923,12 @@ describe("LoopsClient", () => {
describe("createTransactionalEmail", () => {
it("should create a transactional email", async () => {
const mockResponse = {
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Welcome Email",
- draftEmailMessageId: "msg_draft_123",
- draftEmailMessageContentRevisionId: "rev_123",
+ draftEmailMessageId: "clm8k2n4p000yl70f6g7h8i9j",
+ draftEmailMessageContentRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
publishedEmailMessageId: null,
+ transactionalGroupId: null,
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-01T00:00:00.000Z",
dataVariables: [],
@@ -953,10 +957,11 @@ describe("LoopsClient", () => {
describe("updateTransactionalEmail", () => {
it("should update a transactional email", async () => {
const mockResponse = {
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Updated Email",
- draftEmailMessageId: "msg_draft_123",
- publishedEmailMessageId: "msg_pub_123",
+ draftEmailMessageId: "clm8k2n4p000yl70f6g7h8i9j",
+ publishedEmailMessageId: "clm8k2n4p001yl70k1l2m3n4o",
+ transactionalGroupId: null,
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-02T00:00:00.000Z",
dataVariables: ["name"],
@@ -967,13 +972,13 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.updateTransactionalEmail("trans_123", {
+ const result = await client.updateTransactionalEmail("clfn0k1yg001imo0fdeqg30i8", {
name: "Updated Email",
});
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/transactional-emails/trans_123"),
+ expect.stringContaining("v1/transactional-emails/clfn0k1yg001imo0fdeqg30i8"),
expect.objectContaining({
method: "POST",
body: JSON.stringify({ name: "Updated Email" }),
@@ -985,11 +990,12 @@ describe("LoopsClient", () => {
describe("ensureTransactionalEmailDraft", () => {
it("should ensure a transactional email draft exists", async () => {
const mockResponse = {
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Welcome Email",
- draftEmailMessageId: "msg_draft_123",
- draftEmailMessageContentRevisionId: "rev_123",
- publishedEmailMessageId: "msg_pub_123",
+ draftEmailMessageId: "clm8k2n4p000yl70f6g7h8i9j",
+ draftEmailMessageContentRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
+ publishedEmailMessageId: "clm8k2n4p001yl70k1l2m3n4o",
+ transactionalGroupId: null,
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-02T00:00:00.000Z",
dataVariables: ["name"],
@@ -1000,11 +1006,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.ensureTransactionalEmailDraft("trans_123");
+ const result = await client.ensureTransactionalEmailDraft("clfn0k1yg001imo0fdeqg30i8");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/transactional-emails/trans_123/draft"),
+ expect.stringContaining("v1/transactional-emails/clfn0k1yg001imo0fdeqg30i8/draft"),
expect.objectContaining({ method: "POST" })
);
});
@@ -1013,10 +1019,11 @@ describe("LoopsClient", () => {
describe("publishTransactionalEmail", () => {
it("should publish a transactional email draft", async () => {
const mockResponse = {
- id: "trans_123",
+ id: "clfn0k1yg001imo0fdeqg30i8",
name: "Welcome Email",
draftEmailMessageId: null,
- publishedEmailMessageId: "msg_pub_123",
+ publishedEmailMessageId: "clm8k2n4p001yl70k1l2m3n4o",
+ transactionalGroupId: null,
createdAt: "2023-01-01T00:00:00.000Z",
updatedAt: "2023-01-02T00:00:00.000Z",
dataVariables: ["name"],
@@ -1027,11 +1034,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.publishTransactionalEmail("trans_123");
+ const result = await client.publishTransactionalEmail("clfn0k1yg001imo0fdeqg30i8");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/transactional-emails/trans_123/publish"),
+ expect.stringContaining("v1/transactional-emails/clfn0k1yg001imo0fdeqg30i8/publish"),
expect.objectContaining({ method: "POST" })
);
});
@@ -1040,7 +1047,7 @@ describe("LoopsClient", () => {
describe("createUpload", () => {
it("should create an upload", async () => {
const mockResponse = {
- emailAssetId: "asset_123",
+ emailAssetId: "clu7f1w3y011yl70m5n6o7p8q",
presignedUrl: "https://example.com/upload",
};
@@ -1071,8 +1078,8 @@ describe("LoopsClient", () => {
describe("completeUpload", () => {
it("should complete an upload", async () => {
const mockResponse = {
- emailAssetId: "asset_123",
- finalUrl: "https://cdn.example.com/asset_123.png",
+ emailAssetId: "clu7f1w3y011yl70m5n6o7p8q",
+ finalUrl: "https://cdn.example.com/clu7f1w3y011yl70m5n6o7p8q.png",
};
global.fetch = jest.fn().mockResolvedValue({
@@ -1080,11 +1087,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.completeUpload("asset_123");
+ const result = await client.completeUpload("clu7f1w3y011yl70m5n6o7p8q");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/uploads/asset_123/complete"),
+ expect.stringContaining("v1/uploads/clu7f1w3y011yl70m5n6o7p8q/complete"),
expect.objectContaining({ method: "POST" })
);
});
@@ -1112,7 +1119,6 @@ describe("LoopsClient", () => {
describe("listThemes", () => {
it("should list themes with pagination", async () => {
const mockResponse = {
- success: true,
pagination: {
totalResults: 1,
returnedResults: 1,
@@ -1123,7 +1129,7 @@ describe("LoopsClient", () => {
},
data: [
{
- themeId: "theme_123",
+ id: "clo1z5q7s004yl70y3z4a5b6c",
name: "Default",
styles: { backgroundColor: "#ffffff" },
isDefault: true,
@@ -1151,8 +1157,7 @@ describe("LoopsClient", () => {
describe("getTheme", () => {
it("should get a theme by ID", async () => {
const mockResponse = {
- success: true,
- themeId: "theme_123",
+ id: "clo1z5q7s004yl70y3z4a5b6c",
name: "Default",
styles: {},
isDefault: true,
@@ -1165,11 +1170,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.getTheme("theme_123");
+ const result = await client.getTheme("clo1z5q7s004yl70y3z4a5b6c");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/themes/theme_123"),
+ expect.stringContaining("v1/themes/clo1z5q7s004yl70y3z4a5b6c"),
expect.objectContaining({ method: "GET" })
);
});
@@ -1178,7 +1183,6 @@ describe("LoopsClient", () => {
describe("listComponents", () => {
it("should list components with pagination", async () => {
const mockResponse = {
- success: true,
pagination: {
totalResults: 1,
returnedResults: 1,
@@ -1189,7 +1193,7 @@ describe("LoopsClient", () => {
},
data: [
{
- componentId: "comp_123",
+ id: "clp2a6r8t005yl70d7e8f9g0h",
name: "Header",
lmx: "",
},
@@ -1214,8 +1218,7 @@ describe("LoopsClient", () => {
describe("getComponent", () => {
it("should get a component by ID", async () => {
const mockResponse = {
- success: true,
- componentId: "comp_123",
+ id: "clp2a6r8t005yl70d7e8f9g0h",
name: "Header",
lmx: "",
};
@@ -1225,20 +1228,33 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.getComponent("comp_123");
+ const result = await client.getComponent("clp2a6r8t005yl70d7e8f9g0h");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/components/comp_123"),
+ expect.stringContaining("v1/components/clp2a6r8t005yl70d7e8f9g0h"),
expect.objectContaining({ method: "GET" })
);
});
});
+ const campaignFixture = {
+ id: "cln0y4p6r003yl70i1j2k3l4m",
+ name: "Spring announcement",
+ status: "Draft",
+ createdAt: "2025-01-01T00:00:00.000Z",
+ updatedAt: "2025-01-01T00:00:00.000Z",
+ emailMessageId: "clm9x3o5q002yl70a8b3c4d5e",
+ 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 +1263,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,14 +1284,8 @@ 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",
- emailMessageContentRevisionId: "rev_123",
+ ...campaignFixture,
+ emailMessageContentRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
};
global.fetch = jest.fn().mockResolvedValue({
@@ -1308,26 +1308,18 @@ 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,
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.getCampaign("camp_123");
+ const result = await client.getCampaign("cln0y4p6r003yl70i1j2k3l4m");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/campaigns/camp_123"),
+ expect.stringContaining("v1/campaigns/cln0y4p6r003yl70i1j2k3l4m"),
expect.objectContaining({ method: "GET" })
);
});
@@ -1336,13 +1328,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({
@@ -1350,13 +1338,13 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.updateCampaign("camp_123", {
+ const result = await client.updateCampaign("cln0y4p6r003yl70i1j2k3l4m", {
name: "Updated name",
});
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/campaigns/camp_123"),
+ expect.stringContaining("v1/campaigns/cln0y4p6r003yl70i1j2k3l4m"),
expect.objectContaining({
method: "POST",
body: JSON.stringify({ name: "Updated name" }),
@@ -1368,16 +1356,16 @@ describe("LoopsClient", () => {
describe("getEmailMessage", () => {
it("should get an email message by ID", async () => {
const mockResponse = {
- success: true,
- emailMessageId: "msg_123",
- campaignId: "camp_123",
+ id: "clm9x3o5q002yl70a8b3c4d5e",
+ campaignId: "cln0y4p6r003yl70i1j2k3l4m",
subject: "Hello",
previewText: "Preview",
fromName: "Loops",
fromEmail: "hello",
replyToEmail: "",
+ emailFormat: "styled",
lmx: "",
- contentRevisionId: "rev_123",
+ contentRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
updatedAt: "2025-01-01T00:00:00.000Z",
};
@@ -1386,11 +1374,11 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.getEmailMessage("msg_123");
+ const result = await client.getEmailMessage("clm9x3o5q002yl70a8b3c4d5e");
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/email-messages/msg_123"),
+ expect.stringContaining("v1/email-messages/clm9x3o5q002yl70a8b3c4d5e"),
expect.objectContaining({ method: "GET" })
);
});
@@ -1399,16 +1387,16 @@ describe("LoopsClient", () => {
describe("updateEmailMessage", () => {
it("should update an email message", async () => {
const mockResponse = {
- success: true,
- emailMessageId: "msg_123",
- campaignId: "camp_123",
+ id: "clm9x3o5q002yl70a8b3c4d5e",
+ campaignId: "cln0y4p6r003yl70i1j2k3l4m",
subject: "Updated subject",
previewText: "Preview",
fromName: "Loops",
fromEmail: "hello",
replyToEmail: "",
+ emailFormat: "styled",
lmx: "",
- contentRevisionId: "rev_456",
+ contentRevisionId: "clv8g2x4z013yl70s9t0u1v2w",
updatedAt: "2025-01-02T00:00:00.000Z",
warnings: [
{
@@ -1424,19 +1412,19 @@ describe("LoopsClient", () => {
text: () => Promise.resolve(JSON.stringify(mockResponse)),
});
- const result = await client.updateEmailMessage("msg_123", {
- expectedRevisionId: "rev_123",
+ const result = await client.updateEmailMessage("clm9x3o5q002yl70a8b3c4d5e", {
+ expectedRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
subject: "Updated subject",
lmx: "",
});
expect(result).toEqual(mockResponse);
expect(fetch).toHaveBeenCalledWith(
- expect.stringContaining("v1/email-messages/msg_123"),
+ expect.stringContaining("v1/email-messages/clm9x3o5q002yl70a8b3c4d5e"),
expect.objectContaining({
method: "POST",
body: JSON.stringify({
- expectedRevisionId: "rev_123",
+ expectedRevisionId: "clv8g2x4z012yl70n5o6p7q8r",
subject: "Updated subject",
lmx: "",
}),
@@ -1457,7 +1445,7 @@ describe("LoopsClient", () => {
});
await expect(
- client.updateEmailMessage("msg_123", { subject: "Updated" })
+ client.updateEmailMessage("clm9x3o5q002yl70a8b3c4d5e", { subject: "Updated" })
).rejects.toThrow(APIError);
});
});
diff --git a/src/index.ts b/src/index.ts
index 3c5ee23..ebc5494 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[];
}
@@ -1012,6 +1127,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,13 +1135,19 @@ 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,
});
}
@@ -1034,7 +1156,8 @@ class LoopsClient {
*
* @param {string} transactionalId 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
*
@@ -1042,12 +1165,23 @@ class LoopsClient {
*/
async updateTransactionalEmail(
transactionalId: string,
- { name }: { name: 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}`,
method: "POST",
- payload: { name },
+ payload,
});
}
@@ -1100,6 +1234,51 @@ 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} audienceSegmentId The ID of the audience segment.
+ *
+ * @see https://loops.so/docs/api-reference/get-audience-segment
+ *
+ * @returns {Object} Audience segment (JSON)
+ */
+ async getAudienceSegment(
+ audienceSegmentId: string
+ ): Promise {
+ return this._makeQuery({
+ path: `v1/audience-segments/${audienceSegmentId}`,
+ });
+ }
+
/**
* List email themes.
*
@@ -1219,16 +1398,50 @@ 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,
});
}
@@ -1248,11 +1461,16 @@ class LoopsClient {
}
/**
- * Update a draft campaign's name.
+ * Update a draft campaign.
*
* @param {string} campaignId 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
*
@@ -1260,12 +1478,42 @@ class LoopsClient {
*/
async updateCampaign(
campaignId: string,
- { name }: { name: 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}`,
method: "POST",
- payload: { name },
+ payload,
});
}
@@ -1295,7 +1543,14 @@ 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
*
@@ -1310,7 +1565,14 @@ class LoopsClient {
fromName,
fromEmail,
replyToEmail,
+ ccEmail,
+ bccEmail,
+ languageCode,
+ emailFormat,
lmx,
+ contactPropertiesFallbacks,
+ eventPropertiesFallbacks,
+ dataVariablesFallbacks,
}: {
expectedRevisionId?: string;
subject?: string;
@@ -1318,7 +1580,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 +1597,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,7 +1613,20 @@ 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/${emailMessageId}`,
method: "POST",
@@ -1345,6 +1634,319 @@ class LoopsClient {
});
}
+ /**
+ * Send a preview of an email message.
+ *
+ * @param {string} emailMessageId 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(
+ emailMessageId: 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/${emailMessageId}/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} workflowId The ID of the workflow.
+ *
+ * @see https://loops.so/docs/api-reference/get-workflow
+ *
+ * @returns {Object} Workflow graph (JSON)
+ */
+ async getWorkflow(workflowId: string): Promise {
+ return this._makeQuery({
+ path: `v1/workflows/${workflowId}`,
+ });
+ }
+
+ /**
+ * 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} campaignGroupId The ID of the campaign group.
+ *
+ * @see https://loops.so/docs/api-reference/get-campaign-group
+ *
+ * @returns {Object} Campaign group (JSON)
+ */
+ async getCampaignGroup(campaignGroupId: string): Promise {
+ return this._makeQuery({
+ path: `v1/campaign-groups/${campaignGroupId}`,
+ });
+ }
+
+ /**
+ * Update a campaign group.
+ *
+ * @param {string} campaignGroupId 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(
+ campaignGroupId: 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/${campaignGroupId}`,
+ 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} transactionalGroupId The ID of the transactional group.
+ *
+ * @see https://loops.so/docs/api-reference/get-transactional-group
+ *
+ * @returns {Object} Transactional group (JSON)
+ */
+ async getTransactionalGroup(
+ transactionalGroupId: string
+ ): Promise {
+ return this._makeQuery({
+ path: `v1/transactional-groups/${transactionalGroupId}`,
+ });
+ }
+
+ /**
+ * Update a transactional group.
+ *
+ * @param {string} transactionalGroupId 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(
+ transactionalGroupId: 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/transactional-groups/${transactionalGroupId}`,
+ method: "POST",
+ payload,
+ });
+ }
+
/**
* Create an upload.
*
@@ -1427,6 +2029,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,