send-test-email.mjs
1 #!/usr/bin/env node 2 /** 3 * One-shot script: send a representative sample outreach email to deliverability 4 * test addresses (mail-tester.com, Email on Acid, etc.) 5 * 6 * Usage: 7 * node scripts/send-test-email.mjs 8 * node scripts/send-test-email.mjs recipient@example.com 9 */ 10 11 import '../src/utils/load-env.js'; 12 13 const {RESEND_API_KEY} = process.env; 14 if (!RESEND_API_KEY) { console.error('RESEND_API_KEY not set'); process.exit(1); } 15 16 const SENDER_NAME = process.env.SENDER_NAME || 'Marcus Webb'; 17 const SENDER_EMAIL = process.env.SENDER_EMAIL || 'marcus@auditandfix.com'; 18 const SIGNATURE = (process.env.EMAIL_SIGNATURE || `Best regards,\n${SENDER_NAME}\nAudit&Fix`).replace(/\\n/g, '\n'); 19 const PHYSICAL_ADDR = process.env.CAN_SPAM_PHYSICAL_ADDRESS || ''; 20 const UNSUBSCRIBE_URL = 'https://auditandfix.com/unsubscribe?id=test&token=abc123test'; 21 22 const SUBJECT = "your electrician website isn't converting visitors"; 23 24 const BODY = `Hi there, 25 26 Your tarelectric.ca website scored 51.4/100 (D grade). We've analysed 43,000+ local business sites, and your homepage lacks clear trust signals and a compelling unique selling proposition. 27 28 Nothing is broken, but you could be missing out on up to 22-32% more leads just from the way your homepage and call-to-action are structured above the fold. 29 30 I do simple Website Conversion Audits that break this down and show exactly what to change for fast lead increases. It's a one-time service — 24-hour turnaround — and the audit is yours to keep. 31 32 Would you be interested in seeing what I found?`; 33 34 function buildHtml(body, sig, unsubUrl, physAddr, senderName) { 35 const toHtml = t => t.split('\n') 36 .map(l => l.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" style="color:#0066cc;">$1</a>')) 37 .join('<br>'); 38 const physHtml = physAddr 39 ? `<div style="margin-top:10px;font-size:11px;color:#999;">${physAddr}</div>` : ''; 40 return `<!DOCTYPE html> 41 <html> 42 <head> 43 <meta charset="utf-8"> 44 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 45 <title>${senderName} — website review</title> 46 </head> 47 <body style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.6;color:#333;max-width:600px;margin:0 auto;padding:20px;"> 48 <div style="white-space:pre-wrap;">${toHtml(body)}</div> 49 <div style="margin-top:20px;padding-top:20px;border-top:1px solid #eee;white-space:pre-wrap;">${toHtml(sig)}${physHtml}</div> 50 <div style="margin-top:30px;padding-top:20px;border-top:1px solid #eee;font-size:12px;color:#666;"> 51 <p>You received this email because ${senderName} found your business online and believes our web optimisation services may be relevant to you. This is a one-time outreach, not a mailing list.</p> 52 <p>If you'd prefer not to receive emails from us, <a href="${unsubUrl}" style="color:#666;">unsubscribe here</a>.</p> 53 </div> 54 </body> 55 </html>`.trim(); 56 } 57 58 const html = buildHtml(BODY, SIGNATURE, UNSUBSCRIBE_URL, PHYSICAL_ADDR, SENDER_NAME); 59 const text = `${BODY}\n\n${SIGNATURE}${PHYSICAL_ADDR ? `\n\n${ PHYSICAL_ADDR}` : ''}\n\n---\nYou received this email because ${SENDER_NAME} found your business online.\nTo unsubscribe: ${UNSUBSCRIBE_URL}`; 60 61 const targets = process.argv[2] 62 ? [process.argv[2]] 63 : [ 64 'test-vk2xyxbp1@srv1.mail-tester.com', 65 'mailteser+default@precheck.emailonacid.com', 66 ]; 67 68 for (const to of targets) { 69 process.stdout.write(`Sending to ${to}... `); 70 const res = await fetch('https://api.resend.com/emails', { 71 method: 'POST', 72 headers: { 73 Authorization: `Bearer ${RESEND_API_KEY}`, 74 'Content-Type': 'application/json', 75 'User-Agent': '333Method/1.0', 76 }, 77 body: JSON.stringify({ 78 from: `${SENDER_NAME} <${SENDER_EMAIL}>`, 79 to, 80 subject: SUBJECT, 81 html, 82 text, 83 headers: { 84 'List-Unsubscribe': `<${UNSUBSCRIBE_URL}>`, 85 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click', 86 }, 87 }), 88 }); 89 const data = await res.json(); 90 if (res.ok) { 91 console.log(`✓ Resend ID: ${data.id}`); 92 } else { 93 console.error(`✗ ${res.status}: ${JSON.stringify(data)}`); 94 } 95 }