                      first_name="Sendy", last_name=f"Mc{i}",
                      unsub_token=secrets.token_urlsafe(24))
        db.add(r)
        recs.append(r)
    db.commit()
    return rl, recs


def _make_subject(db, user_id, text="Payment reminder %RandomInvoiceNumber%"):
    from app.models import SubjectLine
    s = SubjectLine(user_id=user_id, subject=text)
    db.add(s)
    db.commit()
    return s


def _make_template(db, user_id, body_html=None):
    from app.models import EmailTemplate
    t = EmailTemplate(user_id=user_id, name=f"tpl-{uuid.uuid4().hex[:6]}",
                      subject_hint="hint",
                      body_html=body_html or
---
def _seed_sendable(db, **camp_over):
    """User + list(1) + subject + template + authorized account + campaign +
    materialized CampaignRecipient (pending) + SendJob. Returns ids dict."""
    from app.models import Campaign, CampaignRecipient, SendJob
    u = _make_user(db)
    rl, recs = _make_list(db, u.id, n=1)
    subj = _make_subject(db, u.id)
    tpl = _make_template(db, u.id)
    acc = _make_account(db, u.id)
    kw = dict(
        user_id=u.id, name=f"send-{uuid.uuid4().hex[:6]}", status="running",
        recipient_list_id=rl.id, subject_set_ids=[subj.id], template_ids=[tpl.id],
        account_ids=[acc.id],
        send_interval_seconds=0,  # disable per-account Redis rate window by default
    )
    kw.update(camp_over)
    c = Campaign(**kw)
    db.add(c)
    db.commit()
    cr = CampaignRecipient(campaign_id=c.id, recipient_id=recs[0].id, email=recs[0].email,
                           status="pending", seq=1,
                           generated_subject_id=subj.id, generated_template_id=tpl.id,
                           generated_invoice_number="INV-TEST-0001",
                           generated_amount=123.45, generated_shipping=9.99,
                           generated_phone="+15550001",
                           assigned_account_id=acc.id)
    db.add(cr)
    db.commit()
    job = SendJob(campaign_id=c.id, campaign_recipient_id=cr.id, status="queued",
                  max_attempts=(camp_over.get("max_retries") or 3) + 1)
    db.add(job)
    db.commit()
    return {"user": u, "campaign": c, "cr": cr, "job": job, "account": acc,
            "recipient": recs[0], "subject": subj, "template": tpl}


# ---------------------------------------------------------------------------
# Fake Gmail clients (only the transport is faked; everything else is real)
# ---------------------------------------------------------------------------
class _OkClient:
    """Accepts the message like Gmail would; records the MIME for assertions."""
    method = "api"

    def __init__(self):
        self.sent = []

