← back to Dw Signup Fulfillment
TK-11120: corrected resend tool + apology template (retail-verify localhost outage)
a6edf22ca5d62b163a5a5ee5de51cc42335413a0 · 2026-09-02 13:55:00 -0700 · Steve Abrams
verification/tk11120/resend-corrected.js — safe corrected resender for the 75:
- HARD-REQUIRES PUBLIC_URL (fail-closed baseUrl) + GEORGE_ACCOUNT=info; aborts otherwise
- bypasses the tainted sample_verify_sent flag/old ledger (broken run set them); own ledger
- email.verifyResendEmail: verify letter + one-line apology; DRY_RUN default, --apply gate
- affected-emails.txt: the 75 deduped recipients (Angie excluded)
Proven locally: dry-run builds https link, info@ sender, abort on empty PUBLIC_URL. No send.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
Files touched
M lib/email.jsA verification/tk11120/affected-emails.txtA verification/tk11120/resend-corrected.js
Diff
commit a6edf22ca5d62b163a5a5ee5de51cc42335413a0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 13:55:00 2026 -0700
TK-11120: corrected resend tool + apology template (retail-verify localhost outage)
verification/tk11120/resend-corrected.js — safe corrected resender for the 75:
- HARD-REQUIRES PUBLIC_URL (fail-closed baseUrl) + GEORGE_ACCOUNT=info; aborts otherwise
- bypasses the tainted sample_verify_sent flag/old ledger (broken run set them); own ledger
- email.verifyResendEmail: verify letter + one-line apology; DRY_RUN default, --apply gate
- affected-emails.txt: the 75 deduped recipients (Angie excluded)
Proven locally: dry-run builds https link, info@ sender, abort on empty PUBLIC_URL. No send.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017j4qS38tWq21qYdjxMcFTy
---
lib/email.js | 30 ++++++++++++-
verification/tk11120/affected-emails.txt | 75 ++++++++++++++++++++++++++++++++
verification/tk11120/resend-corrected.js | 73 +++++++++++++++++++++++++++++++
3 files changed, 177 insertions(+), 1 deletion(-)
diff --git a/lib/email.js b/lib/email.js
index d215204..ae6ceac 100644
--- a/lib/email.js
+++ b/lib/email.js
@@ -197,6 +197,34 @@ function verifyEmail({ firstName, url, count }) {
return { subject, html };
}
+// RESEND of the verify letter after the 2026-09-02 dead-localhost-link incident
+// (TK-11120). Identical to verifyEmail but with a short apology line up top, since some
+// recipients replied confused that the earlier link didn't work.
+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 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">
+ <div style="font-size:22px;letter-spacing:3px;font-weight:600">DESIGNER WALLCOVERINGS</div>
+ <div style="font-size:11px;letter-spacing:2px;color:#b8afa2;margin-top:4px">FINE WALLCOVERINGS & FABRICS</div>
+ </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>
+ <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>
+ </div>
+ </div>
+</div>`;
+ return { subject, html };
+}
+
// Confirmation letter sent after a successful /verify — the reward is now live.
function samplesUnlockedEmail({ firstName, count }) {
const fn = firstName ? String(firstName).trim() : '';
@@ -305,4 +333,4 @@ function tradeApplicationEmail({ app, approveUrl, rejectUrl, adminUrl }) {
function money(v) { return `$${Number(v).toFixed(2)}`; }
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); }
-module.exports = { sendEmail, verifyEmail, samplesUnlockedEmail, retailCodeEmail, retailGiftEmail, designerWelcomeEmail, repNotifyEmail, tradeApprovedEmail, tradeRejectedEmail, tradeApplicationEmail, money, esc };
+module.exports = { sendEmail, verifyEmail, verifyResendEmail, samplesUnlockedEmail, retailCodeEmail, retailGiftEmail, designerWelcomeEmail, repNotifyEmail, tradeApprovedEmail, tradeRejectedEmail, tradeApplicationEmail, money, esc };
diff --git a/verification/tk11120/affected-emails.txt b/verification/tk11120/affected-emails.txt
new file mode 100644
index 0000000..468835f
--- /dev/null
+++ b/verification/tk11120/affected-emails.txt
@@ -0,0 +1,75 @@
+alexroudinski@gmail.com
+amyalley1@gmail.com
+annabaynecarouge@gmail.com
+anne.bufordhq@icloud.com
+asht97@aol.com
+astrassel@berwind.com
+beewitched392@gmail.com
+bkarsen4@gmail.com
+bwbd57@gmail.com
+c.stephenson987@outlook.com
+cassie.carlin03@gmail.com
+chelle.fowler@gmail.com
+chris.brezsko@gmail.com
+chris@brooklynfabrication.com
+coatesrx@gmail.com
+cursedjerome@gmail.com
+dbrancati@emergencymdsc.com
+denigeni@outlook.com
+denisestevensonwood@gmail.com
+designteam@gruvercooley.com
+dessasurratt@gmail.com
+emmadykstra15@gmail.com
+eviesmama@yahoo.com
+frontrangewallpaper@gmail.com
+fss5901@aol.com
+garthee@gmail.com
+h.yoshida@mygg.it
+info@adinainteriordesign.com
+info@drycreek.com
+info@loulineinteriors.com
+info@projectluna.de
+jenniferbray214@gmail.com
+jenniferhogan00@yahoo.com
+jill@jilltabor.com
+jlhguy18@gmail.com
+jolainajones@me.com
+jonesfamily2002@verizon.net
+julieguttman2@gmail.com
+karynmouser@gmail.com
+katefiello@gmail.com
+kchlex00@msn.com
+kim@philanthropy.solutions
+lindahleff18@gmail.com
+lizzy@lizzyswicknutrition.com
+lotomimi24@gmail.com
+maccambo4@gmail.com
+maurakessler97@gmail.com
+mck919@aol.com
+michellemooresrq@gmail.com
+mmatthiasfl@gmail.com
+mocowling@gmail.com
+monica.alvarez@gmail.com
+morganld85@gmail.com
+mspalomita@gmail.com
+mwithat@aol.com
+nicole@jacquesmariemage.com
+nmarcoux56@gmail.com
+pamallabondy@gmail.com
+paul.millan@live.co.uk
+pjbennett11@yahoo.com
+shaheenkhosla@gmail.com
+shotcha@gmail.com
+suruchee@polishednest.ca
+talenedw@gmail.com
+talliemac@gmail.com
+taudunoyer@gmail.com
+terrivassar@gmail.com
+thahzeenkhalid@gmail.com
+tiaalford@outlook.com
+tim@timothydeclue.com
+vblack@lasirenapb.com
+vickideur@gmail.com
+wendykramell@yahoo.com
+winniecwtse@gmail.com
+yeoryiayinsella@gmail.com
diff --git a/verification/tk11120/resend-corrected.js b/verification/tk11120/resend-corrected.js
new file mode 100644
index 0000000..ad9b4a3
--- /dev/null
+++ b/verification/tk11120/resend-corrected.js
@@ -0,0 +1,73 @@
+'use strict';
+// TK-11120 — CORRECTED resend of the retail-verify letter to the ~75 customers who got a
+// DEAD http://127.0.0.1:9856 link on 2026-09-02 (the backfill job ran with PUBLIC_URL
+// unset). This sender is safe-by-design and specifically works around the two things that
+// would otherwise break a resend:
+// 1) It HARD-REQUIRES config.PUBLIC_URL (the fail-closed baseUrl now returns '' without
+// it) — so it can never re-ship a localhost link.
+// 2) It BYPASSES the tainted `custom.sample_verify_sent=true` flag + the old backfill
+// ledger (those were set on the broken run), because every one of these customers
+// still needs a WORKING link. Idempotency is instead tracked in this script's OWN
+// ledger so re-runs never double-send.
+// Sender identity: requires GEORGE_ACCOUNT=info so mail is genuinely from info@.
+// Copy: email.verifyResendEmail (adds a one-line apology).
+//
+// Modes:
+// node resend-corrected.js -> DRY_RUN preview of the whole list (no send)
+// node resend-corrected.js --only <email> -> just that address (works for a test addr)
+// node resend-corrected.js --limit N -> first N of the list
+// DRY_RUN=0 ... --apply -> actually send (both required)
+const fs = require('fs');
+const path = require('path');
+const shopify = require(path.join(__dirname, '..', '..', 'lib', 'shopify'));
+const verify = require(path.join(__dirname, '..', '..', 'lib', 'verify'));
+const email = require(path.join(__dirname, '..', '..', 'lib', 'email'));
+const config = require(path.join(__dirname, '..', '..', 'lib', 'config'));
+
+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 LISTP = path.join(__dirname, 'affected-emails.txt');
+const LEDGER = path.join(__dirname, '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'); }
+function mask(e) { return String(e).replace(/(.{2}).*@/, '$1***@'); }
+
+(async () => {
+ // ---- HARD PRE-FLIGHT: never re-ship a bad link or wrong sender ----
+ if (!config.PUBLIC_URL) { console.error('ABORT: PUBLIC_URL is empty — refusing to resend (would ship a dead localhost link). Set PUBLIC_URL=https://signup.designerwallcoverings.com'); process.exit(2); }
+ if (config.GEORGE_ACCOUNT !== 'info') { console.error(`ABORT: GEORGE_ACCOUNT='${config.GEORGE_ACCOUNT}', expected 'info' (Steve directive — send from info@).`); process.exit(2); }
+ const base = verify.baseUrl();
+ if (!base || /127\.0\.0\.1|localhost/.test(base)) { console.error(`ABORT: baseUrl() resolved to '${base}' — not a public https host.`); process.exit(2); }
+
+ let list = ONLY ? [ONLY] : fs.readFileSync(LISTP, 'utf8').split('\n').map(s => s.trim()).filter(Boolean);
+ const done = ledgerDone();
+ console.log(`resend-corrected: ${list.length} target(s) · base=${base} · account=${config.GEORGE_ACCOUNT} · from=${config.GEORGE_FROM} · mode=${config.DRY_RUN ? 'DRY_RUN' : 'LIVE'} · apply=${APPLY} · only=${ONLY || '-'}`);
+ if (!config.DRY_RUN && !APPLY) console.log('LIVE env but no --apply → will only PREVIEW. Add --apply to actually send.');
+ let n = 0, sent = 0, skipped = 0, failed = 0;
+ for (const addr0 of list) {
+ if (n >= LIMIT) break;
+ 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 = '';
+ 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 || ''; }
+ } catch (e) { /* non-fatal — resend with email-prefix greeting */ }
+ 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)}`;
+ const tpl = email.verifyResendEmail({ firstName: firstName || addr.split('@')[0], url, count: config.FREE_SAMPLE_COUNT });
+ if (config.DRY_RUN || !APPLY) { console.log(` [would-send] ${mask(addr)} id=${customerId || '-'} host_ok=${url.startsWith(base)}`); continue; }
+ const r = await email.sendEmail({ to: addr, subject: tpl.subject, html: tpl.html, source: 'retail-verify-resend' });
+ if (r && r.ok !== false) { sent++; ledgerAppend({ email: addr, ok: true, customerId: customerId || null, ts: new Date().toISOString() }); console.log(` [sent] ${mask(addr)} status=${r.status != null ? r.status : (r.dryRun ? 'dry' : '?')}`); }
+ else { failed++; ledgerAppend({ email: addr, ok: false, reason: (r && (r.errorCode || r.error || r.status)) || 'unknown', ts: new Date().toISOString() }); console.log(` [FAIL] ${mask(addr)} status=${r && r.status} err=${r && (r.errorCode || r.error)}`); }
+ await new Promise(res => setTimeout(res, 500)); // gentle pacing for George
+ }
+ console.log(`DONE: targeted=${n} sent=${sent} skipped=${skipped} failed=${failed}`);
+})().catch(e => { console.error('ERR', e.message); process.exit(1); });
← 0016d65 TK-11120: verify links fail-closed on missing PUBLIC_URL + s
·
back to Dw Signup Fulfillment
·
TK-11120: server.js baseUrl() also fails closed on missing P 52afecd →