Skip to content

Add Mailchimp (Actions) cloud destination#3855

Open
mdkhan-tw wants to merge 1 commit into
mainfrom
mz-mailchimp-destination
Open

Add Mailchimp (Actions) cloud destination#3855
mdkhan-tw wants to merge 1 commit into
mainfrom
mz-mailchimp-destination

Conversation

@mdkhan-tw

Copy link
Copy Markdown
Contributor

Summary

Adds a new cloud-mode Mailchimp (Actions) destination for the Mailchimp Marketing API v3.0, with native v2 Journeys compatibility. This replaces the constraints of the Classic Mailchimp destination (hardcoded field mappings, identify-only, no tag management) with flexible merge-field mapping, behavioral tag management, batching, and structured error handling.

Implements the two P0 actions from the PRD:

Actions

Action Trigger Endpoint
Add or Update Audience Member (addOrUpdateMember) identify PUT /lists/{list_id}/members/{subscriber_hash} (batch: POST /lists/{list_id})
Add or Remove Member Tags (addOrRemoveTags) track POST /lists/{list_id}/members/{subscriber_hash}/tags

Key details

  • Auth: API key over HTTP Basic Auth (anystring:apiKey). Datacenter prefix auto-resolved from the API key suffix (e.g. -us6), overridable via a dataCenter setting.
  • Subscriber hash: MD5 of the lowercased email, via the shared processHashing util.
  • Consent-safe: addOrUpdateMember defaults to status_if_new so existing/unsubscribed members' consent status is preserved (CAN-SPAM / GDPR); status is a separate opt-in field.
  • Batching: addOrUpdateMember implements performBatch (POST /lists/{list_id}, up to 500 members, update_existing: true, grouped by list_id). The tags endpoint is strictly per-member with no bulk variant, so addOrRemoveTags has no performBatch.
  • Tags UX: raw {name, status}[] plus tags_to_add / tags_to_remove convenience inputs (tags_to_add defaults to the event name). A 404 (member not found) is surfaced as a clear, actionable APIError.
  • File organization: constants.ts (URLs/enums), types.ts (typed request/response interfaces), utils.ts (send/transform/batch logic); perform/performBatch are thin wrappers.

Testing

  • Unit + snapshot (Jest): 12/12 passing — happy path, missing-email validation, batch, 404 handling, plus request-body snapshots for both actions.
  • Local E2E (./bin/run serve): drove both actions with real HTTP requests. Verified datacenter resolution, subscriber-hash construction, correct single vs batch endpoints, and the dataCenter override reaching the real Mailchimp API (authoritative 403 on a fake key).
  • yarn types, tsc --noEmit, and eslint all clean.

⚠️ TODO before merge

  • Register the destination in packages/destination-actions/src/destinations/index.ts with the production-assigned metadata ID. It is intentionally not registered here: the metadata ID is a real MongoDB ObjectId assigned when the destination is created in Segment production and must match across environments — a placeholder ID would pollute the shared registry.

Out of scope (per PRD)

Outbound email/campaigns, device-mode, reading data back from Mailchimp, audience (list) CRUD, multi-audience fan-out, OAuth 2.0 (future consideration).

🤖 Generated with Claude Code

New cloud-mode destination for Mailchimp Marketing API v3.0 with native
v2 Journeys compatibility. Supports two P0 actions:

- Add or Update Audience Member: upserts a subscriber via
  PUT /lists/{list_id}/members/{subscriber_hash}, mapping traits to merge
  fields. Uses status_if_new to protect existing consent status. Batching
  via POST /lists/{list_id} (up to 500 members, update_existing).
- Add or Remove Member Tags: applies/removes tags via
  POST /lists/{list_id}/members/{subscriber_hash}/tags with active/inactive
  status, plus tags_to_add / tags_to_remove convenience inputs. Surfaces a
  clear error when the member does not exist (404).

Auth is API key over HTTP Basic Auth; the datacenter prefix is resolved
from the API key suffix and overridable via a setting. Subscriber hash is
the MD5 of the lowercased email (via the shared hashing util).

NOTE: not registered in destinations/index.ts yet - the production metadata
ID is assigned when the destination is created in Segment production and must
match across environments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 3, 2026 12:17
@mdkhan-tw mdkhan-tw requested a review from a team as a code owner July 3, 2026 12:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a new cloud-mode Mailchimp (Actions) destination with two actions (member upsert + member tag updates), including batching support for upserts, datacenter resolution, and Jest test coverage.

Changes:

  • Introduces actions-mailchimp destination definition, metadata, and settings/auth handling (Basic Auth + datacenter resolution).
  • Implements addOrUpdateMember (single + batch) and addOrRemoveTags (single) with shared request/transform utilities.
  • Adds unit + snapshot tests for auth, both actions, batching, and 404 behavior.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/destination-actions/src/destinations/actions-mailchimp/index.ts Defines destination, authentication, presets, and action registration.
packages/destination-actions/src/destinations/actions-mailchimp/metadata.json Declares destination metadata and action field schemas/mappings.
packages/destination-actions/src/destinations/actions-mailchimp/constants.ts Adds Mailchimp base URL/path builders and enums.
packages/destination-actions/src/destinations/actions-mailchimp/utils.ts Implements subscriber hashing, request payload shaping, batching, and error handling.
packages/destination-actions/src/destinations/actions-mailchimp/types.ts Adds typed request/response interfaces for Mailchimp endpoints.
packages/destination-actions/src/destinations/actions-mailchimp/generated-types.ts Generated settings types.
packages/destination-actions/src/destinations/actions-mailchimp/addOrUpdateMember/index.ts Implements the “Add or Update Audience Member” action + batching config.
packages/destination-actions/src/destinations/actions-mailchimp/addOrUpdateMember/generated-types.ts Generated payload types for member upsert action.
packages/destination-actions/src/destinations/actions-mailchimp/addOrUpdateMember/tests/index.test.ts Tests single upsert, missing email, and batch request behavior.
packages/destination-actions/src/destinations/actions-mailchimp/addOrRemoveTags/index.ts Implements the “Add or Remove Member Tags” action.
packages/destination-actions/src/destinations/actions-mailchimp/addOrRemoveTags/generated-types.ts Generated payload types for tags action.
packages/destination-actions/src/destinations/actions-mailchimp/addOrRemoveTags/tests/index.test.ts Tests tag payload composition and 404 error surfacing.
packages/destination-actions/src/destinations/actions-mailchimp/tests/index.test.ts Tests testAuthentication behavior including datacenter override.
packages/destination-actions/src/destinations/actions-mailchimp/tests/snapshot.test.ts Snapshot tests for request bodies/headers across actions.
packages/destination-actions/src/destinations/actions-mailchimp/tests/snapshots/snapshot.test.ts.snap Snapshot outputs for the new destination.

Comment on lines +30 to +35
const resolveEmail = (email?: string): string => {
if (!email || email.trim() === '') {
throw new PayloadValidationError('An email address is required to identify the audience member.')
}
return email
}

const buildMemberBody = (payload: MemberPayload): UpsertMemberRequest => {
const body: UpsertMemberRequest = {
email_address: payload.email_address,
Comment on lines +89 to +92
const body: BatchMembersRequest = {
members: members.slice(0, DEFAULT_BATCH_SIZE),
update_existing: true
}
Comment on lines +146 to +155
} catch (error) {
const status = (error as { response?: { status?: number }; status?: number })?.response?.status
if (status === 404) {
throw new APIError(
'Audience member not found. Run the "Add or Update Audience Member" action to create the member before applying tags.',
404
)
}
throw error
}
Comment on lines +106 to +112
if (payload.tags && payload.tags.length > 0) {
for (const tag of payload.tags) {
if (tag && tag.name && tag.name.trim() !== '') {
tags.push({ name: tag.name, status: tag.status })
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants