← back to Designer Wallcoverings
auto-save: 2026-07-27T14:21:14 (1 files) — mailers/cc-api/import-fm-clients.js
4a9a4727158dde0d0be7bb4e9e195d9264a4ba0f · 2026-07-27 14:21:21 -0700 · Steve Abrams
Files touched
A mailers/cc-api/import-fm-clients.js
Diff
commit 4a9a4727158dde0d0be7bb4e9e195d9264a4ba0f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 27 14:21:21 2026 -0700
auto-save: 2026-07-27T14:21:14 (1 files) — mailers/cc-api/import-fm-clients.js
---
mailers/cc-api/import-fm-clients.js | 95 +++++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
diff --git a/mailers/cc-api/import-fm-clients.js b/mailers/cc-api/import-fm-clients.js
new file mode 100644
index 00000000..3f097f96
--- /dev/null
+++ b/mailers/cc-api/import-fm-clients.js
@@ -0,0 +1,95 @@
+#!/usr/bin/env node
+/**
+ * Import the segmented, hygiene-passed FileMaker client list into a new
+ * Constant Contact list. SAFE BY DEFAULT — dry-run (mock) unless BOTH
+ * CC_LIVE=1 and CONFIRM=1 are set. Reads the MAILABLE segment only.
+ *
+ * Dry run (default): node import-fm-clients.js
+ * LIVE import: CC_LIVE=1 CONFIRM=1 node import-fm-clients.js
+ *
+ * Steps (v3 API):
+ * 1. GET /contact_lists → reuse list if the name already exists
+ * 2. POST /contact_lists → else create it, capture list_id
+ * 3. POST /activities/contacts_json_import (batched) permission = implied
+ * 4. report queued activity ids (import runs async on CC's side)
+ */
+const fs = require('fs');
+const path = require('path');
+const cc = require('./cc-client.js');
+
+const MAILABLE = path.join(
+ process.env.HOME,
+ 'Projects/marketing-command-center/data/cc-import-fm-clients-mailable.json'
+);
+const LIST_NAME = 'DW FileMaker Clients (existing relationship)';
+const BATCH = 500;
+const LIVE = process.env.CC_LIVE === '1' && process.env.CONFIRM === '1';
+const API = 'https://api.cc.email/v3';
+
+async function req(method, urlPath, body) {
+ if (!LIVE) {
+ console.log(` [DRY-RUN] ${method} ${urlPath}` + (body ? ` (${JSON.stringify(body).length}b body)` : ''));
+ return { __mock: true };
+ }
+ const token = await cc.getAccessToken();
+ const res = await fetch(`${API}${urlPath}`, {
+ method,
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: 'application/json',
+ ...(body ? { 'Content-Type': 'application/json' } : {}),
+ },
+ ...(body ? { body: JSON.stringify(body) } : {}),
+ });
+ const txt = await res.text();
+ let j; try { j = txt ? JSON.parse(txt) : {}; } catch { j = { raw: txt }; }
+ if (!res.ok) throw new Error(`${method} ${urlPath} → ${res.status}: ${txt}`);
+ return j;
+}
+
+(async () => {
+ const data = JSON.parse(fs.readFileSync(MAILABLE, 'utf8'));
+ const contacts = data.import_data;
+ console.log(`Mode: ${LIVE ? 'LIVE ✅' : 'DRY-RUN (set CC_LIVE=1 CONFIRM=1 to write)'}`);
+ console.log(`Mailable contacts: ${contacts.length.toLocaleString()}`);
+
+ // 1/2 — find or create the list
+ let listId;
+ if (LIVE) {
+ const lr = await req('GET', '/contact_lists?limit=1000');
+ const existing = (lr.lists || []).find((l) => l.name === LIST_NAME);
+ if (existing) { listId = existing.list_id; console.log(`Reusing existing list ${listId}`); }
+ }
+ if (!listId) {
+ const cr = await req('POST', '/contact_lists', {
+ name: LIST_NAME,
+ favorite: false,
+ description: 'DW clients from FileMaker (existing relationship). Hygiene-passed + dead-domain filtered 2026-07-27.',
+ });
+ listId = LIVE ? cr.list_id : 'DRY-RUN-LIST-ID';
+ console.log(`Created list ${listId}`);
+ }
+
+ // 3 — batched json import, permission basis = implied (existing customer)
+ let queued = 0;
+ // CC v3 contacts_json_import schema uses `email` (not `email_address`)
+ const toImport = (r) => {
+ const o = { email: r.email_address || r.email };
+ if (r.company_name) o.company_name = r.company_name;
+ if (r.phone) o.phone = r.phone;
+ return o;
+ };
+ for (let i = 0; i < contacts.length; i += BATCH) {
+ const chunk = contacts.slice(i, i + BATCH).map(toImport);
+ const body = { import_data: chunk, list_ids: [listId] };
+ const r = await req('POST', '/activities/contacts_json_import', body);
+ queued += chunk.length;
+ if (i % (BATCH * 10) === 0 || i + BATCH >= contacts.length) {
+ console.log(` queued ${queued.toLocaleString()}/${contacts.length.toLocaleString()}` +
+ (r.activity_id ? ` (activity ${r.activity_id})` : ''));
+ }
+ }
+ console.log(`\nDone. ${queued.toLocaleString()} contacts submitted to list ${listId}.`);
+ console.log('CC processes imports asynchronously — verify final counts in the CC UI or via GET /activities.');
+ if (!LIVE) console.log('\n(No data was written — this was a dry run.)');
+})().catch((e) => { console.error('IMPORT FAILED:', e.message); process.exit(1); });
← 02da14bc auto-save: 2026-07-27T11:20:11 (1 files) — shopify/scripts/c
·
back to Designer Wallcoverings
·
TK-17: add reversible Batch A (quotes tag) + Batch B (Hollyw 54898819 →