
        subj = subj_sel.next_subject()
        cr.generated_subject_id = subj.id if subj else None
        cr.generated_template_id = tpl.id if tpl else None

        phone = phone_sel.next_phone()
        cr.generated_phone = phone
        g.phone = phone

        account = rot.next_account()
        if account is None:
            cr.status = "skipped"
            cr.last_error = "No available sender account (limits or cooldown)"
        else:
            cr.assigned_account_id = account.id
        rows.append(cr)
        plan.append({"seq": seq, "email": r.email, "invoice": inv_num, "amount": g.amount,
                     "subject": subj.subject if subj else "", "template": tpl.name if tpl else None,
                     "phone": phone, "account": account.email if account else None,
                     "status": cr.status})
        if preview and seq >= (campaign.preview_sample_size or 5):
            break

    db.add_all(rows)
    await db.flush()
    return {"rows": rows, "plan": plan, "rotation": rot.status(),
            "account_count": len(accounts), "subjects": len(subjects),
            "templates": len(templates), "phones": len(phones)}
===GENVALS===
28:class GeneratedValues:
29-    invoice_number: str = ""
30-    amount: Optional[float] = None
31-    shipping: Optional[float] = None
32-    random_name: str = ""
33-    invoice_date: str = ""
34-    due_date: str = ""
35-    phone: str = ""
36-
37-
38-@dataclass
39-class SubstitutionResult:
40-    text: str
41-    unknown: list = field(default_factory=list)
42-    missing: list = field(default_factory=list)   # known but no data available
===CFG===
15:    BASE_URL: str = "http://localhost:8000"
16:    FRONTEND_BASE_URL: str = "http://localhost:8000"
19:    DATABASE_URL: str = "postgresql+psycopg://mailer:mailer_dev_pw@localhost:5432/bulkmailer"
70:    UNSUBSCRIBE_BASE: str = ""  # defaults to BASE_URL
