[object Object]

← back to Designer Wallcoverings

cc-api: add sync-china-seas.js — re-PUT mailer HTML to draft, preserving from/subject/address + trade list

246d40e915315c31e7ea646877e36f1a07e7506d · 2026-06-24 15:16:36 -0700 · Steve

Files touched

Diff

commit 246d40e915315c31e7ea646877e36f1a07e7506d
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 15:16:36 2026 -0700

    cc-api: add sync-china-seas.js — re-PUT mailer HTML to draft, preserving from/subject/address + trade list
---
 mailers/cc-api/sync-china-seas.js | 99 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/mailers/cc-api/sync-china-seas.js b/mailers/cc-api/sync-china-seas.js
new file mode 100644
index 00000000..11f0cd1d
--- /dev/null
+++ b/mailers/cc-api/sync-china-seas.js
@@ -0,0 +1,99 @@
+'use strict';
+/**
+ * sync-china-seas.js — re-PUT the updated mailer HTML onto the existing
+ * China Seas DRAFT activity, PRESERVING from/subject/reply/address AND the
+ * attached contact_list_ids (PUT is full-replace — dropping the list wipes it).
+ *
+ * Phase 1 (default, read-only): discover the China Seas campaign + primary
+ *   activity, GET its current state, print what WOULD change. No write.
+ * Phase 2 (--confirm + CC_LIVE=1): PUT the new HTML, re-including the
+ *   activity's existing contact_list_ids. NOT a send — draft content only.
+ */
+const fs = require('fs');
+const path = require('path');
+try { require('dotenv').config({ path: path.join(__dirname, '.env') }); } catch {}
+
+const cc = require('./cc-client.js');
+const HTML_PATH = path.join(__dirname, '..', 'china-seas-launch.html');
+const CONFIRM = process.argv.includes('--confirm');
+
+// minimal authed fetch reusing the client's token (CC_LIVE gate enforced there)
+async function authed(method, urlPath, body) {
+  const token = await cc.getAccessToken();
+  const url = urlPath.startsWith('http') ? urlPath : `https://api.cc.email/v3${urlPath}`;
+  const res = await fetch(url, {
+    method,
+    headers: {
+      Authorization: `Bearer ${token}`,
+      Accept: 'application/json',
+      ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
+    },
+    ...(body !== undefined ? { 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} ${url} → ${res.status}: ${txt}`);
+  return j;
+}
+
+(async () => {
+  if (process.env.CC_LIVE !== '1') {
+    console.log('CC_LIVE!=1 — read calls NO-OP. Set CC_LIVE=1 to run live.');
+    return;
+  }
+  const html = fs.readFileSync(HTML_PATH, 'utf8');
+  console.log(`loaded HTML: ${html.length} bytes`);
+
+  // 1) find the China Seas campaign
+  const camps = await authed('GET', '/emails?limit=50');
+  const list = camps.campaigns || [];
+  const cs = list.find(c => /china seas/i.test(c.name || ''));
+  if (!cs) { console.log('No China Seas campaign found. Campaigns:', list.map(c => c.name)); return; }
+  console.log(`campaign: "${cs.name}"  id=${cs.campaign_id}  status=${cs.current_status}`);
+
+  // 2) find the primary_email activity id
+  const detail = await authed('GET', `/emails/${cs.campaign_id}`);
+  const acts = detail.campaign_activities || [];
+  const primary = acts.find(a => a.role === 'primary_email') || acts[0];
+  if (!primary) { console.log('No activity found on campaign.'); return; }
+  const activityId = primary.campaign_activity_id;
+  console.log(`primary activity id=${activityId}`);
+
+  // 3) GET current activity state (with html so we can diff size) + list attachment
+  const cur = await authed('GET', `/emails/activities/${activityId}?include=html_content,physical_address_in_footer`);
+  const curLists = cur.contact_list_ids || [];
+  console.log(`current: subject="${cur.subject}"  from=${cur.from_email}  lists=[${curLists.join(', ')}]  html=${(cur.html_content||'').length}b`);
+
+  if (!curLists.length) {
+    console.log('⚠️  WARNING: activity currently has NO contact_list_ids attached.');
+  }
+
+  // 4) build the full-replace payload — preserve everything, swap html only,
+  //    and RE-INCLUDE contact_list_ids so the PUT doesn't wipe the list.
+  const payload = {
+    format_type: 5,
+    from_name: cur.from_name,
+    from_email: cur.from_email,
+    reply_to_email: cur.reply_to_email,
+    subject: cur.subject,
+    ...(cur.preheader ? { preheader: cur.preheader } : {}),
+    html_content: html,
+    physical_address_in_footer: cur.physical_address_in_footer,
+    ...(curLists.length ? { contact_list_ids: curLists } : {}),
+  };
+
+  if (!CONFIRM) {
+    console.log('\n[DRY-RUN] Would PUT updated HTML to activity', activityId);
+    console.log('  preserving from/subject/reply/address; re-including lists:', curLists);
+    console.log('  new html bytes:', html.length);
+    console.log('Run again with --confirm to push (CC_LIVE=1 already set).');
+    return;
+  }
+
+  console.log('\nPUT (live) updated HTML →', activityId);
+  const resp = await authed('PUT', `/emails/activities/${activityId}`, payload);
+  // verify
+  const after = await authed('GET', `/emails/activities/${activityId}?include=html_content`);
+  console.log('✅ updated. after: html=%db  lists=[%s]',
+    (after.html_content||'').length, (after.contact_list_ids||[]).join(', '));
+})().catch(e => { console.error('ERR', e.message); process.exit(1); });

← 33c3f530 China Seas mailer: make all CTA buttons pill-shaped (border-  ·  back to Designer Wallcoverings  ·  patcher: avoid slash in LIVE label so backup path is valid 008d22cf →