Skip to content

Commit aaef4b6

Browse files
committed
fix(mailchimp): archive via DELETE member, add permanent delete, hash member emails and guard path ids, require status_if_new on unarchive, default tag status, stringify batch bodies
1 parent 286ead3 commit aaef4b6

68 files changed

Lines changed: 571 additions & 99 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/blocks/blocks/mailchimp.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export const MailchimpBlock: BlockConfig = {
6363
{ text: 'Delete member', field: 'subscriberEmail', core: true },
6464
{ text: 'from audience', field: 'listId' },
6565
],
66+
delete_member_permanent: [
67+
{ text: 'Permanently delete member', field: 'subscriberEmail', core: true },
68+
{ text: 'from audience', field: 'listId' },
69+
],
6670
archive_member: [
6771
{ text: 'Archive member', field: 'subscriberEmail', core: true },
6872
{ text: 'in audience', field: 'listId' },
@@ -322,6 +326,7 @@ export const MailchimpBlock: BlockConfig = {
322326
{ label: 'Add or Update Member', id: 'add_or_update_member' },
323327
{ label: 'Update Member', id: 'update_member' },
324328
{ label: 'Delete Member', id: 'delete_member' },
329+
{ label: 'Permanently Delete Member', id: 'delete_member_permanent' },
325330
{ label: 'Archive Member', id: 'archive_member' },
326331
{ label: 'Unarchive Member', id: 'unarchive_member' },
327332
// Campaign operations
@@ -425,6 +430,7 @@ export const MailchimpBlock: BlockConfig = {
425430
'add_or_update_member',
426431
'update_member',
427432
'delete_member',
433+
'delete_member_permanent',
428434
'archive_member',
429435
'unarchive_member',
430436
'get_segments',
@@ -467,6 +473,7 @@ export const MailchimpBlock: BlockConfig = {
467473
'add_or_update_member',
468474
'update_member',
469475
'delete_member',
476+
'delete_member_permanent',
470477
'archive_member',
471478
'unarchive_member',
472479
'get_segments',
@@ -656,6 +663,7 @@ Return ONLY the JSON object - no explanations or markdown.`,
656663
'get_member',
657664
'update_member',
658665
'delete_member',
666+
'delete_member_permanent',
659667
'archive_member',
660668
'unarchive_member',
661669
'get_member_tags',
@@ -671,6 +679,7 @@ Return ONLY the JSON object - no explanations or markdown.`,
671679
'get_member',
672680
'update_member',
673681
'delete_member',
682+
'delete_member_permanent',
674683
'archive_member',
675684
'unarchive_member',
676685
'get_member_tags',
@@ -1660,6 +1669,7 @@ Return ONLY the JSON array - no explanations or markdown.`,
16601669
'mailchimp_add_or_update_member',
16611670
'mailchimp_update_member',
16621671
'mailchimp_delete_member',
1672+
'mailchimp_delete_member_permanent',
16631673
'mailchimp_archive_member',
16641674
'mailchimp_unarchive_member',
16651675
'mailchimp_get_campaigns',

‎apps/sim/tools/mailchimp/add_member.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from '@/tools/mailchimp/types'
77
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
88
import type { ToolConfig } from '@/tools/types'
9+
import { safeUrlPathSegment } from '@/tools/url-path'
910

1011
const logger = createLogger('MailchimpAddMember')
1112

@@ -78,7 +79,11 @@ export const mailchimpAddMemberTool: ToolConfig<
7879
},
7980

8081
request: {
81-
url: (params) => buildMailchimpUrl(params.apiKey, `/lists/${params.listId}/members`),
82+
url: (params) =>
83+
buildMailchimpUrl(
84+
params.apiKey,
85+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members`
86+
),
8287
method: 'POST',
8388
headers: (params) => ({
8489
Authorization: `Bearer ${params.apiKey}`,

‎apps/sim/tools/mailchimp/add_member_tags.ts‎

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { createLogger } from '@sim/logger'
2-
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
1+
import {
2+
buildMailchimpUrl,
3+
buildMemberTags,
4+
handleMailchimpError,
5+
mailchimpMemberSegment,
6+
} from '@/tools/mailchimp/types'
37
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
48
import type { ToolConfig } from '@/tools/types'
5-
6-
const logger = createLogger('MailchimpAddMemberTags')
9+
import { safeUrlPathSegment } from '@/tools/url-path'
710

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

5963
request: {
6064
url: (params) =>
6165
buildMailchimpUrl(
6266
params.apiKey,
63-
`/lists/${params.listId}/members/${params.subscriberEmail}/tags`
67+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}/tags`
6468
),
6569
method: 'POST',
6670
headers: (params) => ({
6771
Authorization: `Bearer ${params.apiKey}`,
6872
'Content-Type': 'application/json',
6973
}),
70-
body: (params) => {
71-
let tags = []
72-
try {
73-
tags = JSON.parse(params.tags)
74-
} catch (error) {
75-
logger.warn('Failed to parse tags', { error })
76-
}
77-
78-
return { tags }
79-
},
74+
body: (params) => ({ tags: buildMemberTags(params.tags, 'active') }),
8075
},
8176

8277
transformResponse: async (response: Response) => {

‎apps/sim/tools/mailchimp/add_or_update_member.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
buildMailchimpUrl,
44
handleMailchimpError,
55
type MailchimpMember,
6+
mailchimpMemberSegment,
67
} from '@/tools/mailchimp/types'
78
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
89
import type { ToolConfig } from '@/tools/types'
10+
import { safeUrlPathSegment } from '@/tools/url-path'
911

1012
const logger = createLogger('MailchimpAddOrUpdateMember')
1113

@@ -86,7 +88,10 @@ export const mailchimpAddOrUpdateMemberTool: ToolConfig<
8688

8789
request: {
8890
url: (params) =>
89-
buildMailchimpUrl(params.apiKey, `/lists/${params.listId}/members/${params.subscriberEmail}`),
91+
buildMailchimpUrl(
92+
params.apiKey,
93+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}`
94+
),
9095
method: 'PUT',
9196
headers: (params) => ({
9297
Authorization: `Bearer ${params.apiKey}`,

‎apps/sim/tools/mailchimp/add_segment_member.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@/tools/mailchimp/types'
66
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
77
import type { ToolConfig } from '@/tools/types'
8+
import { safeUrlPathSegment } from '@/tools/url-path'
89

910
export interface MailchimpAddSegmentMemberParams {
1011
apiKey: string
@@ -62,7 +63,7 @@ export const mailchimpAddSegmentMemberTool: ToolConfig<
6263
url: (params) =>
6364
buildMailchimpUrl(
6465
params.apiKey,
65-
`/lists/${params.listId}/segments/${params.segmentId}/members`
66+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/segments/${safeUrlPathSegment(params.segmentId, 'segmentId')}/members`
6667
),
6768
method: 'POST',
6869
headers: (params) => ({

‎apps/sim/tools/mailchimp/add_subscriber_to_automation.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { MailchimpMember } from '@/tools/mailchimp/types'
22
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
33
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
44
import type { ToolConfig } from '@/tools/types'
5+
import { safeUrlPathSegment } from '@/tools/url-path'
56

67
export interface MailchimpAddSubscriberToAutomationParams {
78
apiKey: string
@@ -59,7 +60,7 @@ export const mailchimpAddSubscriberToAutomationTool: ToolConfig<
5960
url: (params) =>
6061
buildMailchimpUrl(
6162
params.apiKey,
62-
`/automations/${params.workflowId}/emails/${params.workflowEmailId}/queue`
63+
`/automations/${safeUrlPathSegment(params.workflowId, 'workflowId')}/emails/${safeUrlPathSegment(params.workflowEmailId, 'workflowEmailId')}/queue`
6364
),
6465
method: 'POST',
6566
headers: (params) => ({

‎apps/sim/tools/mailchimp/archive_member.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
1+
import {
2+
buildMailchimpUrl,
3+
handleMailchimpError,
4+
mailchimpMemberSegment,
5+
} from '@/tools/mailchimp/types'
26
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
37
import type { ToolConfig } from '@/tools/types'
8+
import { safeUrlPathSegment } from '@/tools/url-path'
49

510
export interface MailchimpArchiveMemberParams {
611
apiKey: string
@@ -22,7 +27,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
2227
id: 'mailchimp_archive_member',
2328
name: 'Archive Member from Mailchimp Audience',
2429
description:
25-
'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.',
30+
'Archive a member in a Mailchimp audience. Archived contacts keep their data and can be restored with Unarchive Member.',
2631
version: '1.0.0',
2732
oauth: MAILCHIMP_CREDENTIAL_OAUTH,
2833

@@ -51,7 +56,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
5156
url: (params) =>
5257
buildMailchimpUrl(
5358
params.apiKey,
54-
`/lists/${params.listId}/members/${params.subscriberEmail}/actions/delete-permanent`
59+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/members/${mailchimpMemberSegment(params.subscriberEmail)}`
5560
),
5661
method: 'DELETE',
5762
headers: (params) => ({
@@ -78,7 +83,7 @@ export const mailchimpArchiveMemberTool: ToolConfig<
7883
success: { type: 'boolean', description: 'Operation success status' },
7984
output: {
8085
type: 'object',
81-
description: 'Permanent-deletion request confirmation',
86+
description: 'Archive confirmation',
8287
properties: {
8388
success: { type: 'boolean', description: 'Operation success' },
8489
},

‎apps/sim/tools/mailchimp/create_batch_operation.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const mailchimpCreateBatchOperationTool: ToolConfig<
4242
required: true,
4343
visibility: 'user-or-llm',
4444
description:
45-
'JSON array of batch operations (e.g., [{"method": "POST", "path": "/lists/{list_id}/members", "body": "..."}])',
45+
'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',
4646
},
4747
},
4848

@@ -61,7 +61,16 @@ export const mailchimpCreateBatchOperationTool: ToolConfig<
6161
logger.warn('Failed to parse operations', { error })
6262
}
6363

64-
return { operations }
64+
if (!Array.isArray(operations)) return { operations }
65+
66+
return {
67+
operations: operations.map((operation: unknown) => {
68+
if (!operation || typeof operation !== 'object') return operation
69+
const { body, ...rest } = operation as Record<string, unknown>
70+
if (body === undefined || body === null || typeof body === 'string') return operation
71+
return { ...rest, body: JSON.stringify(body) }
72+
}),
73+
}
6574
},
6675
},
6776

‎apps/sim/tools/mailchimp/create_interest.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { MailchimpInterest } from '@/tools/mailchimp/types'
22
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
33
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
44
import type { ToolConfig } from '@/tools/types'
5+
import { safeUrlPathSegment } from '@/tools/url-path'
56

67
export interface MailchimpCreateInterestParams {
78
apiKey: string
@@ -60,7 +61,7 @@ export const mailchimpCreateInterestTool: ToolConfig<
6061
url: (params) =>
6162
buildMailchimpUrl(
6263
params.apiKey,
63-
`/lists/${params.listId}/interest-categories/${params.interestCategoryId}/interests`
64+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/interest-categories/${safeUrlPathSegment(params.interestCategoryId, 'interestCategoryId')}/interests`
6465
),
6566
method: 'POST',
6667
headers: (params) => ({

‎apps/sim/tools/mailchimp/create_interest_category.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { MailchimpInterestCategory } from '@/tools/mailchimp/types'
22
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
33
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
44
import type { ToolConfig } from '@/tools/types'
5+
import { safeUrlPathSegment } from '@/tools/url-path'
56

67
export interface MailchimpCreateInterestCategoryParams {
78
apiKey: string
@@ -58,7 +59,10 @@ export const mailchimpCreateInterestCategoryTool: ToolConfig<
5859

5960
request: {
6061
url: (params) =>
61-
buildMailchimpUrl(params.apiKey, `/lists/${params.listId}/interest-categories`),
62+
buildMailchimpUrl(
63+
params.apiKey,
64+
`/lists/${safeUrlPathSegment(params.listId, 'listId')}/interest-categories`
65+
),
6266
method: 'POST',
6367
headers: (params) => ({
6468
Authorization: `Bearer ${params.apiKey}`,

0 commit comments

Comments
 (0)