Mailchimp Integration
[To Build] ·
provider-mailchimp(new) ·ConnectedChanneltype addition
Connects a tenant’s Mailchimp account via OAuth and enables approved email newsletters to be pushed directly to Mailchimp as draft campaigns, ready to send to the tenant’s selected audience.
Related: Email Writer · Channels · Repurposing Pipeline · Content Toolkit Overview
Overview
| Function | Push approved email drafts to Mailchimp as campaigns; manage audience selection |
| Type | Provider + Channel Integration |
| Status | To Build |
| Priority | P2 — Differentiating |
| Package | packages/providers/mailchimp/ (new) |
| Credits | 0 cr (delivery action; no LLM inference) |
| Plan | Pro+ |
Why This Is Needed
The email-writer agent produces complete email newsletters — subject line, preview text, structured body sections, CTA. But there is no delivery path: the content is reviewed and approved inside Leadmetrics and then must be manually copied into Mailchimp. Connecting directly to Mailchimp closes the loop, making the email workflow end-to-end: generate → review → approve → push to Mailchimp draft → tenant sends.
The existing provider-sendgrid package handles transactional delivery (system emails). Mailchimp targets the marketing campaign use case — mass audience sends — which is a different API and a different workflow.
OAuth Connect Flow
Mailchimp uses OAuth 2.0. The connect flow follows the same pattern as existing channels (Google, LinkedIn, Meta).
Tenant navigates to Settings → Channels → Connect Mailchimp
↓
Redirect to Mailchimp OAuth: https://login.mailchimp.com/oauth2/authorize
Scopes: N/A — Mailchimp OAuth grants full account access by default
↓
Mailchimp redirects back to: /api/pg/v1/mailchimp/callback
↓
Exchange code for access token (no expiry on Mailchimp tokens)
↓
Fetch datacenter prefix: GET https://login.mailchimp.com/oauth2/metadata
Returns: { dc: "us14" } — prefix used in all API calls
↓
Create ConnectedChannel record:
{
tenantId,
channelType: "Mailchimp",
isConnected: true,
tokenInfo: encrypt({ accessToken, dc }),
accountName: metadata.login.login_email,
}
↓
Dashboard Channels page shows Mailchimp as connectedprovider-mailchimp Package
Location: packages/providers/mailchimp/
Public API:
interface MailchimpProvider {
// Audience management
getAudiences(accessToken: string, dc: string): Promise<MailchimpAudience[]>;
getAudienceSegments(accessToken: string, dc: string, listId: string): Promise<MailchimpSegment[]>;
// Campaign management
createDraftCampaign(
accessToken: string,
dc: string,
params: CreateCampaignParams
): Promise<{ campaignId: string; webId: number; archiveUrl: string }>;
updateCampaignContent(
accessToken: string,
dc: string,
campaignId: string,
html: string
): Promise<void>;
}
interface CreateCampaignParams {
listId: string; // Mailchimp audience ID
segmentId?: number; // Optional segment within the audience
subjectLine: string;
previewText: string;
fromName: string; // e.g. "Acme Marketing"
replyTo: string; // reply-to email address
title: string; // internal campaign name in Mailchimp
}
interface MailchimpAudience {
id: string;
name: string;
memberCount: number;
}
interface MailchimpSegment {
id: number;
name: string;
memberCount: number;
}Push to Mailchimp Workflow
When an EmailActivity transitions to client_approved:
EmailActivity.status → "client_approved"
↓
Check: does this tenant have a ConnectedChannel of type "Mailchimp"?
No → Skip; no error; email remains in "Approved" state in dashboard
Yes → Check: does the EmailActivity have a selectedAudienceId?
No → Email remains in dashboard; show "Select audience to push" prompt
Yes →
↓
Enqueue mailchimp-publisher job:
{ tenantId, emailActivityId, connectedChannelId, audienceId, segmentId? }
↓
mailchimp-publisher worker:
1. Decrypt token from ConnectedChannel.tokenInfo
2. Convert EmailActivity.bodyMarkdown to HTML (marked.js)
3. POST /campaigns → create draft campaign with subject + preview text
4. PUT /campaigns/{id}/content → set HTML body
5. Store mailchimpCampaignId + mailchimpCampaignUrl on EmailActivity
6. Emit WebSocket event: email:pushed_to_mailchimp
↓
Dashboard email detail: "Pushed to Mailchimp" badge + link to Mailchimp campaign editorAudience Selector
When a DM or tenant creates an EmailActivity (or a repurposed email activity is created), they can select which Mailchimp audience and optional segment to target:
UI flow:
- Email activity creation form includes a “Mailchimp Audience” field (shown only if Mailchimp is connected)
- Field fetches audiences via
GET /tenant/v1/channels/mailchimp/audiences - Optional: segment picker rendered after audience selection via
GET /tenant/v1/channels/mailchimp/audiences/:listId/segments selectedAudienceIdand optionalselectedSegmentIdstored on theEmailActivityrecord
DB Changes
Add to the EmailActivity model (or equivalent email content model):
mailchimpCampaignId String?
mailchimpCampaignUrl String?
mailchimpPushedAt DateTime?
selectedAudienceId String?
selectedSegmentId Int?New API Routes
| Method | Path | Description |
|---|---|---|
GET | /tenant/v1/channels/mailchimp/audiences | List Mailchimp audiences for the connected account |
GET | /tenant/v1/channels/mailchimp/audiences/:listId/segments | List segments within a specific audience |
POST | /tenant/v1/email/:id/push-to-mailchimp | Manually trigger push (if auto-push did not fire) |
GET | /api/pg/v1/mailchimp/callback | OAuth callback handler |
Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Push as draft, not send | Campaign is created as a draft in Mailchimp | Leadmetrics is the content platform; the tenant controls when to actually send from Mailchimp |
| No scheduled send | Mailchimp draft only; tenant schedules from Mailchimp UI | Simplifies v1; avoids replicating Mailchimp’s send scheduler |
| Token does not expire | Mailchimp tokens are non-expiring; no refresh needed | Simplifies token management; still encrypted at rest |
| Markdown → HTML in worker | Convert using marked.js in the worker | Keeps the email-writer output clean (Markdown); HTML conversion is a delivery concern |
| One Mailchimp account per tenant | Only one ConnectedChannel of type “Mailchimp” per tenant | Agencies managing multiple clients each on their own Mailchimp account will need a separate tenant per client — consistent with the multi-tenant model |
Implementation Phases
Phase 1 — OAuth Connect
- Create
packages/providers/mailchimp/package - Implement
getAudiences(),createDraftCampaign(),updateCampaignContent()methods - Add Mailchimp to
ChannelMasterin the database seed - Add
/api/pg/v1/mailchimp/callbackOAuth callback route - Channels settings page: “Connect Mailchimp” button
Phase 2 — Push Worker
- Create
apps/api/src/workers/mailchimp-publisher.worker.ts - Add
mailchimpCampaignId,mailchimpCampaignUrl,mailchimpPushedAtto email model (migration) - Hook
client_approvedstatus transition to auto-enqueue mailchimp-publisher if audience is selected - Emit
email:pushed_to_mailchimpWebSocket event - Dashboard email detail: Mailchimp status badge + campaign link
Phase 3 — Audience Selector
- Add
selectedAudienceId,selectedSegmentIdto email model (migration) - Add
GET /tenant/v1/channels/mailchimp/audiencesand segments routes - Email activity creation form: audience + segment picker (shown when Mailchimp connected)
- “Push to Mailchimp” manual action button in email detail page