#!/usr/bin/env node const { readFileSync, writeFileSync, existsSync, mkdirSync } = require('fs'); const { join } = require('path'); const Handlebars = require('handlebars'); const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses'); function parseArgs() { const args = process.argv.slice(2); const out = {}; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a === '--template' && args[i+1]) { out.template = args[++i]; } else if (a === '--sample' && args[i+1]) { out.sample = args[++i]; } else if (a === '--to' && args[i+1]) { out.to = args[++i]; } else if (a === '--send') { out.send = true; } else if (a === '--data' && args[i+1]) { out.dataFile = args[++i]; } } return out; } const opts = parseArgs(); const templateName = opts.template || 'universal-template.html'; const sampleType = opts.sample || 'annulation'; const toEmail = opts.to || process.env.TEST_TO_EMAIL || null; const doSend = !!opts.send; if (!toEmail && doSend) { console.error('To send a real email you must pass --to or set TEST_TO_EMAIL env var'); process.exit(1); } const templatePath = join(process.cwd(), 'templates-mails', templateName); if (!existsSync(templatePath)) { console.error('Template not found:', templatePath); process.exit(1); } const templateSrc = readFileSync(templatePath, 'utf-8'); const template = Handlebars.compile(templateSrc); const samples = { annulation: { subject: 'CDDU annulé', preheaderText: 'CDDU annulé pour Jean Dupont · Réf CDD-123', title: 'CDDU annulé', mainMessage: 'Votre contrat CDDU a été annulé.', closingMessage: "Pour votre sécurité, ne partagez jamais ce message.", ctaText: "Accès à l'Espace Paie", ctaUrl: 'https://example.com/contrat/123', footerText: 'Vous recevez cet e-mail car vous êtes client de Odentas.', logoUrl: 'https://newstaging.odentas.fr/wp-content/uploads/2025/08/Odentas-Logo-Bleu-Fond-Transparent-4-1.png', contractReference: 'CDD-123', employeeName: 'Jean Dupont', profession: 'Développeur', startDate: '2025-10-01', showCard: true, cardTitle: 'Détails du contrat', cardRows: [ [ { label: 'Référence', value: 'CDD-123' } ], [ { label: 'Salarié', value: 'Jean Dupont' } ], [ { label: 'Poste', value: 'Développeur' } ], [ { label: 'Date de début', value: '2025-10-01' } ] ], showChips: false, headerColor: '#efc543', titleColor: '#0F172A', buttonColor: '#efc543', cardBorder: '1px solid #E5E7EB', cardBackgroundColor: '#F8FAFC', cardTitleColor: '#0F172A', alertIndicatorColor: '#EF4444', supportUrl: 'https://example.com/support', userName: 'Jean Dupont', companyName: 'Ma Structure', employerCode: 'EMP-456', productionName: 'Production A', textFallback: 'CDDU annulé – Réf CDD-123 pour Jean Dupont.' } }; let sampleData = samples[sampleType] || samples.annulation; // allow loading a JSON data file with overrides if (opts.dataFile) { try { const raw = readFileSync(join(process.cwd(), opts.dataFile), 'utf8'); const userData = JSON.parse(raw); sampleData = { ...sampleData, ...userData }; } catch (err) { console.warn('Unable to read/parse data file, ignoring:', opts.dataFile); } } const rendered = template(sampleData); // apply data-* attributes to inline styles (simple) function applyDataAttrAsStyle(html, dataAttr, cssProp) { // append to existing style html = html.replace(new RegExp(`(<[^>]*?)${dataAttr}="([^"]*)"([^>]*style=\")([^"]*)(\"[^>]*>)`, 'g'), (_m, prefix, val, mid, styleValue, suffix) => { return `${prefix}${mid}${styleValue} ${cssProp}:${val};${suffix}`; }); // add style when missing html = html.replace(new RegExp(`(<[^>]*?)${dataAttr}="([^"]*)"([^>]*?)>`, 'g'), (_m, prefix, val, suffix) => { return `${prefix} style="${cssProp}:${val};"${suffix}>`; }); html = html.replace(new RegExp(`${dataAttr}="[^"]*"`, 'g'), ''); return html; } let finalHtml = rendered; ['data-header-color','data-alert-indicator','data-title-color','data-card-border','data-card-bg','data-card-title-color','data-alert-title-color','data-alert-text-color','data-button-color'].forEach(attr => { const css = { 'data-header-color':'background', 'data-alert-indicator':'background', 'data-title-color':'color', 'data-card-border':'border', 'data-card-bg':'background', 'data-card-title-color':'color', 'data-alert-title-color':'color', 'data-alert-text-color':'color', 'data-button-color':'background' }[attr]; finalHtml = applyDataAttrAsStyle(finalHtml, attr, css); }); if (!existsSync(join(process.cwd(), 'tmp'))) mkdirSync(join(process.cwd(), 'tmp')); writeFileSync(join(process.cwd(), 'tmp', 'send-preview.html'), finalHtml, 'utf8'); console.log('Wrote preview to tmp/send-preview.html'); if (!doSend) { console.log('Dry-run (not sending). To actually send, run with --send and set AWS env vars and --to '); console.log('Env required for real send: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (optional), FROM_EMAIL'); process.exit(0); } // Real send path (async () => { try { const region = process.env.AWS_REGION || 'eu-west-3'; const ses = new SESClient({ region, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY }}); const source = process.env.FROM_EMAIL || process.env.AWS_SES_FROM; if (!source) throw new Error('Set FROM_EMAIL or AWS_SES_FROM env var to a verified sender'); const params = { Source: source, Destination: { ToAddresses: [toEmail] }, Message: { Subject: { Data: sampleData.subject || 'Test email', Charset: 'UTF-8' }, Body: { Html: { Data: finalHtml, Charset: 'UTF-8' }, Text: { Data: sampleData.textFallback || '', Charset: 'UTF-8' } } } }; const cmd = new SendEmailCommand(params); const res = await ses.send(cmd); console.log('SES send response:', res); } catch (err) { console.error('Error sending via SES:', err); process.exit(1); } })();