[object Object]

← 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

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 →