${numRow("Amount min ($)", "amount_min", 'type="number" step="0.01" min="0"')}
${numRow("Amount max ($)", "amount_max", 'type="number" step="0.01" min="0"')}
${numRow("Shipping min ($)", "shipping_min", 'type="number" step="0.01" min="0"')}
${numRow("Shipping max ($)", "shipping_max", 'type="number" step="0.01" min="0"')}
`;
} else if (step === 7) {
b.innerHTML = H("Step 7 — Gmail accounts") + hint(`Select up to ${OPT.limits.max_accounts} sending accounts. Only authorized, active accounts are shown.`) +
(OPT.accounts.length ? pickList("accounts", C.account_ids, a => ({ v: a.id, main: a.email, sub: `${a.sends_today}/${a.daily_limit} today · ${a.sending_method}` }), "account_ids")
: `⚠No authorized Gmail accounts. Connect one on the Gmail Accounts page first.
`);
} else if (step === 8) {
const strat = C.rotation_strategy;
b.innerHTML = H("Step 8 — Account rotation") + hint("How senders rotate. Weighted lets you favor specific accounts; per-account caps and cooldowns are enforced regardless.") +
`
`;
bindRotExtra();
} else if (step === 9) {
b.innerHTML = H("Step 9 — Limits & pacing") + hint("Conservative pacing protects your accounts. Sends are throttled and retried with exponential backoff.") +
`
${numRow("Max sends per account", "max_per_account", 'type="number" min="1" max="2000"')}
${numRow("Interval between sends (s)", "send_interval_seconds", 'type="number" step="0.5" min="0.5" max="3600"')}
${numRow("Rate cap (per minute)", "rate_per_minute", 'type="number" min="1" max="600"')}
${numRow("Max retries", "max_retries", 'type="number" min="0" max="10"')}
${numRow("Retry delay (s)", "retry_delay_seconds", 'type="number" min="5" max="3600"')}
`;
} else if (step === 10) {
b.innerHTML = H("Step 10 — Company phone") + hint("Which active phone number appears as %CompanyPhone%.") +
(OPT.phones.length ? pickList("phones", C.phone_selection === "fixed" ? (C.variable_config.phone_id ?? null) : null, p => ({ v: p.id, main: p.phone, sub: p.label || "" }), "phone_fixed", true)
: `⚠No active phone numbers. %CompanyPhone% will be blank unless numbers are added on the Company Phones page.
`) +
`
${["round_robin", "random", "fixed"].map(m => `
`).join("")}
`;
} else if (step === 11) {
b.innerHTML = H("Step 11 — Unsubscribe handling") + hint("Permission-based sending requires working opt-out. Disabling is not permitted.") +
`
ℹSuppression list is always checked before sending, regardless of this setting. Recipients who opt out are skipped automatically.
`;
} else if (step === 12) {
const vc = C.variable_config;
const list = OPT.lists.find(l => l.id === C.recipient_list_id);
const accs = (OPT.accounts || []).filter(a => C.account_ids.includes(a.id));
const tpls = (OPT.templates || []).filter(t => C.template_ids.includes(t.id));
const subCount = (OPT.subjects || []).filter(s => C.subject_set_ids.includes(s.id)).length;
const total = list ? list.valid_count : 0;
const cap = Math.max(...(accs.map(a => C.max_per_account).concat([0])), 1);
const capacity = accs.length * cap;
b.innerHTML = H("Step 12 — Confirm and start") + hint("Review the full configuration. You can still edit any step, then create the campaign.") +
`
- Campaign name
- ${esc(C.name) || "—"}
- Recipient list
- ${list ? esc(list.name) + ` (${list.valid_count} valid of ${list.row_count})` : "—"}
- Subjects
- ${subCount} selected · ${esc(C.subject_rotation)}
- Templates
- ${tpls.length ? tpls.map(t => esc(t.name)).join(", ") : "—"} · ${esc(C.template_sequence_mode)}
- Message type
- ${esc(C.message_type)}
- Attachments
- ${C.attach_mode === "none" ? "none" : esc(C.attach_mode) +
(C.attach_mode === "pdf" && C.attach_pdf_template_id ? ` — template #${C.attach_pdf_template_id}` : "") +
(C.attach_mode !== "none" && C.attach_mode !== "pdf" && C.attachment_id ? ` — attachment #${C.attachment_id}` : "")}
- Invoice format
- ${esc(vc.invoice_format || "{prefix}{R4}-{R5}-{R6}-{seq4}")} · prefix ${esc(vc.invoice_prefix || "INV-")}
- Amount range
- ${esc(vc.amount_min ?? 300)} – ${esc(vc.amount_max ?? 1500)} · shipping ${esc(vc.shipping_min ?? 5)} – ${esc(vc.shipping_max ?? 50)}
- Accounts
- ${accs.length ? accs.map(a => esc(a.email)).join(", ") : "—"} · ${esc(C.rotation_strategy)}${C.rotation_strategy === "weighted" ? ` (weights ${esc(JSON.stringify(C.rotation_weights))})` : ""}
- Per-account cap
- ${C.max_per_account} → capacity ${capacity} of ${total} recipients
- Pacing
- ${C.send_interval_seconds}s interval · ${C.rate_per_minute}/min · ${C.max_retries} retries @ ${C.retry_delay_seconds}s
- Phone
- ${esc(C.phone_selection)}
- Unsubscribe
- ${C.unsubscribe_enabled ? "enabled" : "disabled"}
${capacity < total ? `
⚠Account capacity (${capacity}) is below the recipient count (${total}). The campaign will stop when capacity is exhausted.
` : ""}
ℹAfter creating, the campaign opens in the live monitor where you press Start. No email leaves until you do.
`;
}
b.scrollTop = 0;
bindStep();
}
function pickList(kind, current, mapper, stateKey, radio = false) {
const items = OPT[kind] || [];
if (!items.length) return ``;
const cur = Array.isArray(current) ? current : (current ? [current] : []);
return `` + items.map(it => {
const m = mapper(it);