[object Object]

← back to Dw Signup Fulfillment

TK-11120: retail-only 5-sample apology resend (--force, --retail-only) — 74 sent from info@

0b127cf286016d32f31171dcdce879b9e71579b3 · 2026-09-02 14:50:36 -0700 · Steve Abrams

- verifyResendEmail: light-hearted apology copy (Steve-approved)
- resend-corrected.js: --retail-only (skip trade tag), --force (own 5-sample ledger)
- prove-real-customer.js: --setup <email> / --delete <id> modes for live click proof
Sent light-hearted 5-sample letter to 74 retail (1 trade skipped), from info@, links verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy

Files touched

Diff

commit 0b127cf286016d32f31171dcdce879b9e71579b3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 2 14:50:36 2026 -0700

    TK-11120: retail-only 5-sample apology resend (--force, --retail-only) — 74 sent from info@
    
    - verifyResendEmail: light-hearted apology copy (Steve-approved)
    - resend-corrected.js: --retail-only (skip trade tag), --force (own 5-sample ledger)
    - prove-real-customer.js: --setup <email> / --delete <id> modes for live click proof
    Sent light-hearted 5-sample letter to 74 retail (1 trade skipped), from info@, links verified.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
---
 lib/email.js                                | 10 +++++-----
 verification/tk11120/prove-real-customer.js | 15 +++++++++++++++
 verification/tk11120/resend-corrected.js    | 15 ++++++++++-----
 3 files changed, 30 insertions(+), 10 deletions(-)

diff --git a/lib/email.js b/lib/email.js
index ae6ceac..048f6f2 100644
--- a/lib/email.js
+++ b/lib/email.js
@@ -203,7 +203,7 @@ function verifyEmail({ firstName, url, count }) {
 function verifyResendEmail({ firstName, url, count }) {
   const fn = firstName ? String(firstName).trim() : '';
   const greet = fn ? esc(fn.charAt(0).toUpperCase() + fn.slice(1)) : 'there';
-  const subject = `Here's your working link — your ${count} complimentary samples`;
+  const subject = `Well, that was awkward — here's your (working!) link + ${count} free samples 🎁`;
   const html = `<div style="font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:600px;margin:0 auto">
   <div style="border:1px solid #e2ddd4;border-radius:10px;overflow:hidden">
     <div style="background:#1a1a1a;color:#fff;text-align:center;padding:26px 20px">
@@ -212,13 +212,13 @@ function verifyResendEmail({ firstName, url, count }) {
     </div>
     <div style="padding:26px 28px;font-size:15px;line-height:1.7;color:#2a2a2a">
       <p style="margin:0 0 14px">Dear ${greet},</p>
-      <p style="margin:0 0 14px;color:#8a6d1f;background:rgba(212,160,74,0.10);border-left:3px solid #d4a04a;padding:10px 14px;border-radius:2px">Our earlier confirmation link didn't work — apologies for that. Here's the correct one:</p>
-      <p style="margin:0 0 8px">Your account includes <b>${count} lifetime complimentary samples</b>. Confirm this email, then sign in with the same address when you shop:</p>
+      <p style="margin:0 0 14px">Well, this is a little embarrassing. 🙈 The "confirm your email" link we just sent you led absolutely nowhere — a broken link on our end, entirely our fault, not yours.</p>
+      <p style="margin:0 0 14px">So let's make it right: we've bumped you up to <b>${count} complimentary samples</b> (yes, ${count === 5 ? 'five' : count} — a little extra for the trouble). And here's a link that <i>actually works</i> this time:</p>
       <div style="text-align:center;margin:22px 0 8px">
         <a href="${esc(url)}" style="background:#1a1a1a;color:#fff;text-decoration:none;padding:13px 34px;border-radius:30px;font-size:14px;letter-spacing:1px;display:inline-block">Confirm my email</a>
       </div>
-      <p style="margin:14px 0 0;color:#555;font-size:13px">When signed in, up to your remaining lifetime allowance shows free automatically at checkout—no code needed. Approved design professionals receive unlimited complimentary samples. This confirmation link expires in a few days. If you didn't create an account, ignore this email.</p>
-      <p style="margin:20px 0 0;color:#2a2a2a">Warmly,<br><b>The Designer Wallcoverings Team</b></p>
+      <p style="margin:14px 0 0;color:#555;font-size:13px">Once you're confirmed, just sign in with this address when you shop and your samples show free at checkout automatically — no code, no fuss. This link expires in a few days. If you didn't create an account, feel free to ignore this (and our apologies for the extra email).</p>
+      <p style="margin:20px 0 0;color:#2a2a2a">Thanks for your patience,<br><b>The Designer Wallcoverings Team</b></p>
     </div>
   </div>
 </div>`;
diff --git a/verification/tk11120/prove-real-customer.js b/verification/tk11120/prove-real-customer.js
index 68aa732..a531dfa 100644
--- a/verification/tk11120/prove-real-customer.js
+++ b/verification/tk11120/prove-real-customer.js
@@ -23,7 +23,22 @@ function getVerify(path) {
   return new Promise((resolve) => { https.get(PUBLIC + path, res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve({ status: res.statusCode, body: d })); }).on('error', e => resolve({ status: 0, error: e.message })); });
 }
 
+const argv = process.argv.slice(2);
+const SETUP = argv.includes('--setup');           // create + print ID/URL, do NOT click or delete
+const DELID = argv.includes('--delete') ? argv[argv.indexOf('--delete') + 1] : null; // delete a customer by id
+
 (async () => {
+  if (DELID) { const d = await shopify('DELETE', `customers/${DELID}.json`); console.log('deleted id=', DELID, '→ status', d.status); process.exit(d.status === 200 ? 0 : 1); }
+  if (SETUP) {
+    const email = argv.find(a => a.includes('@')) || `steve+tk11120-show-${Date.now()}@designerwallcoverings.com`;
+    const c = await shopify('POST', 'customers.json', { customer: { email, first_name: 'Steve', verified_email: true } });
+    const cust = c.json && c.json.customer;
+    if (!cust || !cust.id) { console.log('CREATE FAILED', c.status, JSON.stringify(c.json)); process.exit(1); }
+    const token = verify.mintToken({ email, customerId: String(cust.id) });
+    console.log('ID=' + cust.id);
+    console.log('URL=' + PUBLIC + '/verify?token=' + encodeURIComponent(token));
+    process.exit(0);
+  }
   const email = `steve+tk11120-realcust-${Date.now()}@designerwallcoverings.com`;
   console.log('1) creating throwaway Shopify customer:', email);
   const c = await shopify('POST', 'customers.json', { customer: { email, first_name: 'RealTest', verified_email: true } });
diff --git a/verification/tk11120/resend-corrected.js b/verification/tk11120/resend-corrected.js
index ad9b4a3..8b28107 100644
--- a/verification/tk11120/resend-corrected.js
+++ b/verification/tk11120/resend-corrected.js
@@ -28,8 +28,11 @@ const args = process.argv.slice(2);
 const APPLY = args.includes('--apply');
 const ONLY = args.includes('--only') ? args[args.indexOf('--only') + 1] : null;
 const LIMIT = args.includes('--limit') ? parseInt(args[args.indexOf('--limit') + 1], 10) : Infinity;
+const FORCE = args.includes('--force');            // ignore the prior resend ledger (the 3-sample run)
+const RETAIL_ONLY = args.includes('--retail-only'); // skip customers tagged 'trade' (they get unlimited)
 const LISTP = path.join(__dirname, 'affected-emails.txt');
-const LEDGER = path.join(__dirname, 'tk11120-resend-ledger.jsonl');
+// The 5-sample upgrade run uses its OWN ledger so it stays idempotent without the 3-sample run's entries.
+const LEDGER = path.join(__dirname, FORCE ? 'tk11120-5sample-ledger.jsonl' : 'tk11120-resend-ledger.jsonl');
 
 function ledgerDone() { const s = new Set(); try { for (const l of fs.readFileSync(LEDGER, 'utf8').split('\n')) { if (!l.trim()) continue; const r = JSON.parse(l); if (r.ok) s.add(String(r.email).toLowerCase()); } } catch {} return s; }
 function ledgerAppend(row) { fs.appendFileSync(LEDGER, JSON.stringify(row) + '\n'); }
@@ -52,13 +55,15 @@ function mask(e) { return String(e).replace(/(.{2}).*@/, '$1***@'); }
     const addr = String(addr0).trim().toLowerCase();
     if (!addr) continue;
     if (done.has(addr)) { skipped++; console.log(`  [skip] ${mask(addr)} already resent (ledger)`); continue; }
-    n++;
-    // Look up the Shopify customer for id + first name (best-effort; resend regardless).
-    let customerId = null, firstName = '';
+    // Look up the Shopify customer for id + first name + tags (best-effort).
+    let customerId = null, firstName = '', tags = '';
     try {
       customerId = await shopify.findCustomerByEmail(addr);
-      if (customerId) { const g = await shopify.getCustomer(customerId); const c = g && g.json && g.json.customer; if (c) firstName = c.first_name || ''; }
+      if (customerId) { const g = await shopify.getCustomer(customerId); const c = g && g.json && g.json.customer; if (c) { firstName = c.first_name || ''; tags = c.tags || ''; } }
     } catch (e) { /* non-fatal — resend with email-prefix greeting */ }
+    // RETAIL-ONLY (Steve): trade/designer accounts get UNLIMITED samples, so the 5-sample offer doesn't apply.
+    if (RETAIL_ONLY && tags.split(',').map(t => t.trim().toLowerCase()).includes('trade')) { skipped++; console.log(`  [skip] ${mask(addr)} trade account (retail-only)`); continue; }
+    n++;
     const token = verify.mintToken({ email: addr, customerId });
     if (!token) { failed++; ledgerAppend({ email: addr, ok: false, reason: 'no_token(secret?)', ts: new Date().toISOString() }); console.log(`  [FAIL] ${mask(addr)} could not mint token (VERIFY_SECRET?)`); continue; }
     const url = `${base}/verify?token=${encodeURIComponent(token)}`;

← 64b3102 TK-11120: real-customer click proof — verify confirms + tags  ·  back to Dw Signup Fulfillment  ·  TK-11120 Option B: verify page shows the token-carried sampl d1ffb0f →