/** * CM-1: Mailchimp Marketing API integration (one-way push, CRM → Mailchimp). * * SCOPE NOTE: per the locked CM-1 decision, the exact group → tag/segment * mapping is finalised only once we have the client's actual Mailchimp account. * So this module ships the config plumbing + an inert sync that no-ops until * (a) an admin stores an API key + audience ID and (b) the mapping is wired. * The members viewer + copy-emails features do NOT depend on Mailchimp. * * Settings keys (per-port, in system_settings): * - `mailchimp_api_key` (AES-encrypted at rest, like SMTP/IMAP creds) * - `mailchimp_audience_id` (the single audience all groups map into) */ import { logger } from '@/lib/logger'; import { getSetting } from '@/lib/services/settings.service'; import { decrypt } from '@/lib/utils/encryption'; export interface MailchimpConfig { apiKey: string; audienceId: string; /** Datacenter prefix derived from the key suffix (e.g. `us21`). */ serverPrefix: string; } /** Resolve + decrypt the per-port Mailchimp config, or null when unset. */ export async function getMailchimpConfig(portId: string): Promise { const keyRow = await getSetting('mailchimp_api_key', portId); const audRow = await getSetting('mailchimp_audience_id', portId); const encKey = typeof keyRow?.value === 'string' ? keyRow.value : null; const audienceId = typeof audRow?.value === 'string' ? audRow.value : null; if (!encKey || !audienceId) return null; let apiKey: string; try { apiKey = decrypt(encKey); } catch { return null; } // Mailchimp keys are `-`; the datacenter is the API host prefix. const serverPrefix = apiKey.split('-')[1] ?? ''; if (!serverPrefix) return null; return { apiKey, audienceId, serverPrefix }; } export async function isMailchimpConfigured(portId: string): Promise { return (await getMailchimpConfig(portId)) !== null; } export type MailchimpSyncResult = { skipped: string } | { synced: true; count: number }; /** * Push a group's members to Mailchimp as a tag/segment on the port's audience. * Inert until configured AND the mapping is confirmed (see SCOPE NOTE). */ export async function syncGroupToMailchimp( groupId: string, portId: string, ): Promise { const config = await getMailchimpConfig(portId); if (!config) return { skipped: 'not-configured' }; // TODO(CM-1): mapping pending the client's Mailchimp account. Once confirmed, // upsert each member via // PUT https://{serverPrefix}.api.mailchimp.com/3.0/lists/{audienceId}/members/{md5(lowercased-email)} // then apply the group's tag. Only push subscribed/opted-in contacts (GDPR). logger.info({ groupId, portId }, 'Mailchimp sync requested (mapping pending client account)'); return { skipped: 'mapping-pending' }; }