← 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
A mailers/cc-api/sync-china-seas.js
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 →