Skip to content

Commit 0663ac6

Browse files
committed
fix(mailchimp): reject empty tag and batch inputs instead of sending no-ops, keep numeric tag names, document the archive and permanent delete actions
1 parent aaef4b6 commit 0663ac6

4 files changed

Lines changed: 42 additions & 16 deletions

File tree

‎apps/docs/content/docs/integrations/mailchimp.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ Use [Mailchimp](https://mailchimp.com/) in Sim to manage audiences and members,
1414

1515
## Member removal
1616

17-
**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.
17+
**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.
1818

19-
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.
19+
**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.
2020
{/* MANUAL-CONTENT-END */}
2121

2222
## Use the block [#usage-instructions]

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { createLogger } from '@sim/logger'
21
import type { MailchimpBatchOperation } from '@/tools/mailchimp/types'
32
import { buildMailchimpUrl, handleMailchimpError } from '@/tools/mailchimp/types'
43
import { MAILCHIMP_CREDENTIAL_OAUTH } from '@/tools/shared/saved-credentials'
54
import type { ToolConfig } from '@/tools/types'
65

7-
const logger = createLogger('MailchimpCreateBatchOperation')
8-
96
export interface MailchimpCreateBatchOperationParams {
107
apiKey: string
11-
operations: string
8+
operations: string | unknown[]
129
}
1310

1411
export interface MailchimpCreateBatchOperationResponse {
@@ -54,14 +51,18 @@ export const mailchimpCreateBatchOperationTool: ToolConfig<
5451
'Content-Type': 'application/json',
5552
}),
5653
body: (params) => {
57-
let operations = []
58-
try {
59-
operations = JSON.parse(params.operations)
60-
} catch (error) {
61-
logger.warn('Failed to parse operations', { error })
54+
let operations: unknown = params.operations
55+
if (typeof operations === 'string') {
56+
try {
57+
operations = JSON.parse(operations)
58+
} catch {
59+
throw new Error('Operations must be a JSON array of batch operations')
60+
}
6261
}
6362

64-
if (!Array.isArray(operations)) return { operations }
63+
if (!Array.isArray(operations)) {
64+
throw new Error('Operations must be a JSON array of batch operations')
65+
}
6566

6667
return {
6768
operations: operations.map((operation: unknown) => {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,24 @@ describe('Mailchimp request bodies', () => {
8989
})
9090
})
9191

92+
it('keeps numeric tag names and rejects input with no tag names', () => {
93+
const params = { apiKey, listId: 'list1', subscriberEmail: janeHash }
94+
expect(bodyOf(mailchimpAddMemberTagsTool, { ...params, tags: '2024' })).toEqual({
95+
tags: [{ name: '2024', status: 'active' }],
96+
})
97+
expect(() => bodyOf(mailchimpAddMemberTagsTool, { ...params, tags: '[]' })).toThrow()
98+
})
99+
100+
it('accepts an already-parsed operations array and rejects unparseable operations', () => {
101+
const operations = [{ method: 'GET', path: '/lists' }]
102+
expect(bodyOf(mailchimpCreateBatchOperationTool, { apiKey, operations })).toEqual({
103+
operations,
104+
})
105+
expect(() =>
106+
bodyOf(mailchimpCreateBatchOperationTool, { apiKey, operations: 'not json' })
107+
).toThrow()
108+
})
109+
92110
it('serializes an object batch operation body to the JSON string Mailchimp requires', () => {
93111
const body = bodyOf(mailchimpCreateBatchOperationTool, {
94112
apiKey,

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,18 +405,25 @@ export function buildMemberTags(
405405
}
406406
}
407407
const items = Array.isArray(parsed) ? parsed : [parsed]
408-
return items.flatMap((item) => {
409-
if (typeof item === 'string') {
410-
const name = item.trim()
408+
const tags = items.flatMap((item) => {
409+
if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
410+
const name = String(item).trim()
411411
return name ? [{ name, status: defaultStatus }] : []
412412
}
413413
if (item && typeof item === 'object' && 'name' in item) {
414414
const tag = item as { name: unknown; status?: unknown }
415+
const name =
416+
typeof tag.name === 'string' || typeof tag.name === 'number' ? String(tag.name).trim() : ''
417+
if (!name) return []
415418
const status = typeof tag.status === 'string' && tag.status ? tag.status : defaultStatus
416-
return [{ name: String(tag.name), status }]
419+
return [{ name, status }]
417420
}
418421
return []
419422
})
423+
if (tags.length === 0) {
424+
throw new Error('Tags must include at least one tag name')
425+
}
426+
return tags
420427
}
421428

422429
// Helper function for consistent error handling

0 commit comments

Comments
 (0)