← back to Marketing Command Center
cc-import: stage gated FM-client → Constant Contact import (34,825 contacts; scraped list excluded); upload drafted to pending-approval (DTD verdict A)
47459ec486479ce7b3ea537fc134c118ef95936a · 2026-07-17 09:48:40 -0700 · steve
Files touched
M .gitignoreA scripts/cc-import-fm-preview.js
Diff
commit 47459ec486479ce7b3ea537fc134c118ef95936a
Author: steve <steve@designerwallcoverings.com>
Date: Fri Jul 17 09:48:40 2026 -0700
cc-import: stage gated FM-client → Constant Contact import (34,825 contacts; scraped list excluded); upload drafted to pending-approval (DTD verdict A)
---
.gitignore | 1 +
scripts/cc-import-fm-preview.js | 65 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
diff --git a/.gitignore b/.gitignore
index 0a22b4d..6999c27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,4 @@ data/sources/
# Python bytecode
__pycache__/
*.pyc
+data/cc-import-fm-clients.json
diff --git a/scripts/cc-import-fm-preview.js b/scripts/cc-import-fm-preview.js
new file mode 100644
index 0000000..3e07f46
--- /dev/null
+++ b/scripts/cc-import-fm-preview.js
@@ -0,0 +1,65 @@
+#!/usr/bin/env node
+/*
+ * cc-import-fm-preview.js — STAGE (do not upload) a Constant Contact import of
+ * the FileMaker CLIENT list only. DTD verdict A (2026-07-17): build the gated
+ * import scoped to the CAN-SPAM-legit existing-relationship client list; the
+ * scraped prospect emails are NOT included and stay held.
+ *
+ * This ONLY reads clients-fm.json and writes a CC-ready payload + stats. It makes
+ * ZERO calls to Constant Contact. The actual upload is a separate Steve-gated
+ * step (see the pending-approval memo).
+ */
+const fs = require('fs');
+const path = require('path');
+
+const SRC = path.join(__dirname, '..', 'public', 'data', 'clients-fm.json');
+const OUT = path.join(__dirname, '..', 'data', 'cc-import-fm-clients.json');
+const LIST_NAME = 'DW FileMaker Clients (existing relationship)';
+
+const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/;
+const clean = s => String(s == null ? '' : s).trim();
+const raw = JSON.parse(fs.readFileSync(SRC, 'utf8'));
+const clients = raw.clients || [];
+
+const seen = new Set();
+const import_data = [];
+let noEmail = 0, badEmail = 0, dupe = 0;
+for (const c of clients) {
+ const email = clean(c.email).toLowerCase();
+ if (!email) { noEmail++; continue; }
+ if (!EMAIL_RE.test(email)) { badEmail++; continue; }
+ if (seen.has(email)) { dupe++; continue; }
+ seen.add(email);
+ const name = clean(c.name);
+ let first = '', last = '';
+ if (name) { const p = name.split(/\s+/); first = p.shift() || ''; last = p.join(' '); }
+ const rec = { email_address: email };
+ if (first) rec.first_name = first.slice(0, 50);
+ if (last) rec.last_name = last.slice(0, 50);
+ const company = clean(c.company); if (company) rec.company_name = company.slice(0, 50);
+ const phone = clean(c.phone); if (phone) rec.phone = phone.slice(0, 25);
+ import_data.push(rec);
+}
+
+const payload = {
+ staged_at: new Date().toISOString(),
+ source: 'clients-fm.json (FileMaker Clients)',
+ list_name: LIST_NAME,
+ can_spam_basis: 'existing business relationship (clients who ordered samples/products from Designer Wallcoverings)',
+ scraped_excluded: true,
+ total_fm_records: clients.length,
+ with_valid_unique_email: import_data.length,
+ dropped: { no_email: noEmail, bad_email: badEmail, duplicate: dupe },
+ named: import_data.filter(r => r.first_name || r.last_name).length,
+ with_company: import_data.filter(r => r.company_name).length,
+ // CC v3 shape: POST /activities/contacts_json_import { import_data, list_ids:[<newListId>] }
+ import_data,
+};
+fs.mkdirSync(path.dirname(OUT), { recursive: true });
+fs.writeFileSync(OUT, JSON.stringify(payload));
+console.log(`✓ staged ${path.relative(process.cwd(), OUT)} (NO upload — CC untouched)`);
+console.log(` target list: "${LIST_NAME}"`);
+console.log(` ${import_data.length.toLocaleString()} valid unique-email contacts of ${clients.length.toLocaleString()} FM records`);
+console.log(` dropped: ${noEmail.toLocaleString()} no-email · ${badEmail.toLocaleString()} bad-email · ${dupe.toLocaleString()} duplicate`);
+console.log(` ${payload.named.toLocaleString()} have a contact name · ${payload.with_company.toLocaleString()} have a company`);
+console.log(` scraped prospect emails: EXCLUDED (held)`);
← c333624 auto-save: 2026-07-17T09:46:05 (2 files) — modules/channels/
·
back to Marketing Command Center
·
instagram: poll media container status until FINISHED before c1d7a1d →