Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions apps/docs/content/docs/integrations/mailchimp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Use [Mailchimp](https://mailchimp.com/) in Sim to manage audiences and members,

## Member removal

**Delete Member from Mailchimp Audience** calls Mailchimp's reversible archive endpoint. **Archive Member from Mailchimp Audience** instead targets permanent deletion of identifying data, after which the member cannot be re-imported. These UI labels differ from the provider's terminology.
**Delete Member from Mailchimp Audience** and **Archive Member from Mailchimp Audience** both call Mailchimp's reversible [archive endpoint](https://mailchimp.com/developer/marketing/api/list-members/archive-list-member); an archived member can be added back.

The current permanent-deletion action sends `DELETE`, while [Mailchimp documents `POST` for delete-permanent](https://mailchimp.com/developer/marketing/api/lists/members/create-member-action-delete-permanent). Its compatibility is unverified; do not rely on it for deletion until the request method is corrected and tested.
**Permanently Delete Member from Mailchimp Audience** calls [`POST .../actions/delete-permanent`](https://mailchimp.com/developer/marketing/api/list-member-actions/delete-list-member-permanently), which erases the member's identifying data. It cannot be undone, and the address cannot be re-imported.
{/* MANUAL-CONTENT-END */}

## Use the block [#usage-instructions]
Expand Down Expand Up @@ -307,9 +307,32 @@ Archive a member from a Mailchimp audience without permanently deleting their da

</ReferenceDetails>

### Permanently Delete Member from Mailchimp Audience

Permanently delete a member and their personally identifiable data from a Mailchimp audience. This cannot be undone, and the address cannot be re-imported.

<ReferenceDetails>

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `listId` | string | Yes | The unique ID for the audience/list \(e.g., "abc123def4"\) |
| `subscriberEmail` | string | Yes | Member email address or MD5 hash of the lowercase email |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Permanent deletion confirmation |
| ↳ `success` | boolean | Operation success |

</ReferenceDetails>

### Archive Member from Mailchimp Audience

Request permanent deletion of a member and their identifying data. This targets delete-permanent, despite the Archive Member label; provider acceptance of the current HTTP method is unverified.
Archive a member in a Mailchimp audience. Archived contacts keep their data and can be restored with Unarchive Member.

<ReferenceDetails>

Expand All @@ -325,7 +348,7 @@ Request permanent deletion of a member and their identifying data. This targets
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Permanent-deletion request confirmation |
| `output` | object | Archive confirmation |
| ↳ `success` | boolean | Operation success |

</ReferenceDetails>
Expand Down Expand Up @@ -1137,7 +1160,7 @@ Add tags to a member in a Mailchimp audience
| --------- | ---- | -------- | ----------- |
| `listId` | string | Yes | The unique ID for the audience/list \(e.g., "abc123def4"\) |
| `subscriberEmail` | string | Yes | Member email address or MD5 hash of the lowercase email |
| `tags` | string | Yes | JSON array of tag objects \(e.g., \[\{"name": "VIP", "status": "active"\}\]\) |
| `tags` | string | Yes | JSON array of tag objects \(e.g., \[\{"name": "VIP", "status": "active"\}\]\) or a comma-separated list of tag names; status defaults to "active" |

#### Output

Expand All @@ -1161,7 +1184,7 @@ Remove tags from a member in a Mailchimp audience
| --------- | ---- | -------- | ----------- |
| `listId` | string | Yes | The unique ID for the audience/list \(e.g., "abc123def4"\) |
| `subscriberEmail` | string | Yes | Member email address or MD5 hash of the lowercase email |
| `tags` | string | Yes | JSON array of tag objects with inactive status \(e.g., \[\{"name": "VIP", "status": "inactive"\}\]\) |
| `tags` | string | Yes | JSON array of tag objects with inactive status \(e.g., \[\{"name": "VIP", "status": "inactive"\}\]\) or a comma-separated list of tag names; status defaults to "inactive" |

#### Output

Expand Down Expand Up @@ -1766,7 +1789,7 @@ Create a new batch operation in Mailchimp

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `operations` | string | Yes | JSON array of batch operations \(e.g., \[\{"method": "POST", "path": "/lists/\{list_id\}/members", "body": "..."\}\]\) |
| `operations` | string | Yes | JSON array of batch operations \(e.g., \[\{"method": "POST", "path": "/lists/\{list_id\}/members", "body": \{"email_address": "user@example.com", "status": "subscribed"\}\}\]\); an object body is sent as the JSON string Mailchimp expects |

#### Output

Expand Down
10 changes: 10 additions & 0 deletions apps/sim/blocks/blocks/mailchimp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export const MailchimpBlock: BlockConfig = {
{ text: 'Delete member', field: 'subscriberEmail', core: true },
{ text: 'from audience', field: 'listId' },
],
delete_member_permanent: [
{ text: 'Permanently delete member', field: 'subscriberEmail', core: true },
{ text: 'from audience', field: 'listId' },
],
archive_member: [
{ text: 'Archive member', field: 'subscriberEmail', core: true },
{ text: 'in audience', field: 'listId' },
Expand Down Expand Up @@ -322,6 +326,7 @@ export const MailchimpBlock: BlockConfig = {
{ label: 'Add or Update Member', id: 'add_or_update_member' },
{ label: 'Update Member', id: 'update_member' },
{ label: 'Delete Member', id: 'delete_member' },
{ label: 'Permanently Delete Member', id: 'delete_member_permanent' },
{ label: 'Archive Member', id: 'archive_member' },
{ label: 'Unarchive Member', id: 'unarchive_member' },
// Campaign operations
Expand Down Expand Up @@ -425,6 +430,7 @@ export const MailchimpBlock: BlockConfig = {
'add_or_update_member',
'update_member',
'delete_member',
'delete_member_permanent',
'archive_member',
'unarchive_member',
'get_segments',
Expand Down Expand Up @@ -467,6 +473,7 @@ export const MailchimpBlock: BlockConfig = {
'add_or_update_member',
'update_member',
'delete_member',
'delete_member_permanent',
'archive_member',
'unarchive_member',
'get_segments',
Expand Down Expand Up @@ -656,6 +663,7 @@ Return ONLY the JSON object - no explanations or markdown.`,
'get_member',
'update_member',
'delete_member',
'delete_member_permanent',
'archive_member',
'unarchive_member',
'get_member_tags',
Expand All @@ -671,6 +679,7 @@ Return ONLY the JSON object - no explanations or markdown.`,
'get_member',
'update_member',
'delete_member',
'delete_member_permanent',
'archive_member',
'unarchive_member',
'get_member_tags',
Expand Down Expand Up @@ -1660,6 +1669,7 @@ Return ONLY the JSON array - no explanations or markdown.`,
'mailchimp_add_or_update_member',
'mailchimp_update_member',
'mailchimp_delete_member',
'mailchimp_delete_member_permanent',
'mailchimp_archive_member',
'mailchimp_unarchive_member',
'mailchimp_get_campaigns',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/generated/tool-ids.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/sim/tools/generated/tool-metadata.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/sim/tools/generated/tool-outputs.ts

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion apps/sim/tools/mailchimp/add_member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

const logger = createLogger('MailchimpAddMember')

Expand Down Expand Up @@ -78,7 +79,11 @@ export const mailchimpAddMemberTool: ToolConfig<
},

request: {
url: (params) => buildMailchimpUrl(params.apiKey, `/lists/${params.listId}/members`),
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members`
),
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Expand Down
27 changes: 11 additions & 16 deletions apps/sim/tools/mailchimp/add_member_tags.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { createLogger } from '@sim/logger'
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
import {
buildMailchimpUrl,
buildMemberTags,
handleMailchimpError,
mailchimpMemberSegment,
} from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'

const logger = createLogger('MailchimpAddMemberTags')
import { safeUrlPathSegment } from '@/tools/url-path'

export interface MailchimpAddMemberTagsParams {
apiKey: string
Expand Down Expand Up @@ -52,31 +55,23 @@ export const mailchimpAddMemberTagsTool: ToolConfig<
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'JSON array of tag objects (e.g., [{"name": "VIP", "status": "active"}])',
description:
'JSON array of tag objects (e.g., [{"name": "VIP", "status": "active"}]) or a comma-separated list of tag names; status defaults to "active"',
},
},

request: {
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/lists/${params.listId}/members/${params.subscriberEmail}/tags`
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}/tags`
),
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let tags = []
try {
tags = JSON.parse(params.tags)
} catch (error) {
logger.warn('Failed to parse tags', { error })
}

return { tags }
},
body: (params) => ({ tags: buildMemberTags(params.tags, 'active') }),
},

transformResponse: async (response: Response) => {
Expand Down
7 changes: 6 additions & 1 deletion apps/sim/tools/mailchimp/add_or_update_member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {
buildMailchimpUrl,
handleMailchimpError,
type MailchimpMember,
mailchimpMemberSegment,
} from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

const logger = createLogger('MailchimpAddOrUpdateMember')

Expand Down Expand Up @@ -86,7 +88,10 @@ export const mailchimpAddOrUpdateMemberTool: ToolConfig<

request: {
url: (params) =>
buildMailchimpUrl(params.apiKey, `/lists/${params.listId}/members/${params.subscriberEmail}`),
buildMailchimpUrl(
params.apiKey,
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}`
),
method: 'PUT',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/tools/mailchimp/add_segment_member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

export interface MailchimpAddSegmentMemberParams {
apiKey: string
Expand Down Expand Up @@ -62,7 +63,7 @@ export const mailchimpAddSegmentMemberTool: ToolConfig<
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/lists/${params.listId}/segments/${params.segmentId}/members`
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/segments/${safeUrlPathSegment(params.segmentId, 'segmentId')}/members`
),
method: 'POST',
headers: (params) => ({
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/tools/mailchimp/add_subscriber_to_automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MailchimpMember } from '@/tools/mailchimp/types'
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

export interface MailchimpAddSubscriberToAutomationParams {
apiKey: string
Expand Down Expand Up @@ -59,7 +60,7 @@ export const mailchimpAddSubscriberToAutomationTool: ToolConfig<
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/automations/${params.workflowId}/emails/${params.workflowEmailId}/queue`
`/automations/${safeUrlPathSegment(params.workflowId, 'workflowId')}/emails/${safeUrlPathSegment(params.workflowEmailId, 'workflowEmailId')}/queue`
),
method: 'POST',
headers: (params) => ({
Expand Down
13 changes: 9 additions & 4 deletions apps/sim/tools/mailchimp/archive_member.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
import {
buildMailchimpUrl,
handleMailchimpError,
mailchimpMemberSegment,
} from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

export interface MailchimpArchiveMemberParams {
apiKey: string
Expand All @@ -22,7 +27,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
id: 'mailchimp_archive_member',
name: 'Archive Member from Mailchimp Audience',
description:
'Request permanent deletion of a member and their identifying data. This targets delete-permanent, despite the Archive Member label; provider acceptance of the current HTTP method is unverified.',
'Archive a member in a Mailchimp audience. Archived contacts keep their data and can be restored with Unarchive Member.',
version: '1.0.0',
oauth: MAILCHIMP_CREDENTIAL_OAUTH,

Expand Down Expand Up @@ -51,7 +56,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/lists/${params.listId}/members/${params.subscriberEmail}/actions/delete-permanent`
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}`
),
method: 'DELETE',
headers: (params) => ({
Expand All @@ -78,7 +83,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
success: { type: 'boolean', description: 'Operation success status' },
output: {
type: 'object',
description: 'Permanent-deletion request confirmation',
description: 'Archive confirmation',
properties: {
success: { type: 'boolean', description: 'Operation success' },
},
Expand Down
32 changes: 21 additions & 11 deletions apps/sim/tools/mailchimp/create_batch_operation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { createLogger } from '@sim/logger'
import type { MailchimpBatchOperation } from '@/tools/mailchimp/types'
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'

const logger = createLogger('MailchimpCreateBatchOperation')

export interface MailchimpCreateBatchOperationParams {
apiKey: string
operations: string
operations: string | unknown[]
}

export interface MailchimpCreateBatchOperationResponse {
Expand Down Expand Up @@ -42,7 +39,7 @@ export const mailchimpCreateBatchOperationTool: ToolConfig<
required: true,
visibility: 'user-or-llm',
description:
'JSON array of batch operations (e.g., [{"method": "POST", "path": "/lists/{list_id}/members", "body": "..."}])',
'JSON array of batch operations (e.g., [{"method": "POST", "path": "/lists/{list_id}/members", "body": {"email_address": "user@example.com", "status": "subscribed"}}]); an object body is sent as the JSON string Mailchimp expects',
},
},

Expand All @@ -54,14 +51,27 @@ export const mailchimpCreateBatchOperationTool: ToolConfig<
'Content-Type': 'application/json',
}),
body: (params) => {
let operations = []
try {
operations = JSON.parse(params.operations)
} catch (error) {
logger.warn('Failed to parse operations', { error })
let operations: unknown = params.operations
if (typeof operations === 'string') {
try {
operations = JSON.parse(operations)
} catch {
throw new Error('Operations must be a JSON array of batch operations')
}
}

if (!Array.isArray(operations)) {
throw new Error('Operations must be a JSON array of batch operations')
}

return { operations }
return {
operations: operations.map((operation: unknown) => {
if (!operation || typeof operation !== 'object') return operation
const { body, ...rest } = operation as Record<string, unknown>
if (body === undefined || body === null || typeof body === 'string') return operation
return { ...rest, body: JSON.stringify(body) }
}),
}
},
},

Expand Down
3 changes: 2 additions & 1 deletion apps/sim/tools/mailchimp/create_interest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MailchimpInterest } from '@/tools/mailchimp/types'
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
import type { ToolConfig } from '@/tools/types'
import { safeUrlPathSegment } from '@/tools/url-path'

export interface MailchimpCreateInterestParams {
apiKey: string
Expand Down Expand Up @@ -60,7 +61,7 @@ export const mailchimpCreateInterestTool: ToolConfig<
url: (params) =>
buildMailchimpUrl(
params.apiKey,
`/lists/${params.listId}/interest-categories/${params.interestCategoryId}/interests`
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/interest-categories/${safeUrlPathSegment(params.interestCategoryId, 'interestCategoryId')}/interests`
),
method: 'POST',
headers: (params) => ({
Expand Down
Loading
Loading